Use public version of AppFuse in MtpDocumentsProvider.
AppFuse is now a public API. The CL removes the private version from
MtpDocuemntsProvider package and switches to the public version.
Bug: 32891253
Test: MtpDocumentsProviderTest
Change-Id: Ibdf67309bc0678e2f70ac2dddb920125d9e0760e
diff --git a/Android.mk b/Android.mk
index c9e7195..a9e9b2e 100644
--- a/Android.mk
+++ b/Android.mk
@@ -6,7 +6,6 @@
LOCAL_PACKAGE_NAME := MtpDocumentsProvider
LOCAL_CERTIFICATE := media
LOCAL_PRIVILEGED_MODULE := true
-LOCAL_JNI_SHARED_LIBRARIES := libappfuse_jni
LOCAL_PROGUARD_FLAG_FILES := proguard.flags
# Only enable asserts on userdebug/eng builds
diff --git a/jni/Android.mk b/jni/Android.mk
deleted file mode 100644
index d545b14..0000000
--- a/jni/Android.mk
+++ /dev/null
@@ -1,15 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := \
- com_android_mtp_AppFuse.cpp
-
-LOCAL_SHARED_LIBRARIES := \
- libandroid_runtime \
- libnativehelper \
- liblog
-
-LOCAL_CFLAGS += -Wall -Werror -Wunused -Wunreachable-code
-LOCAL_MODULE := libappfuse_jni
-
-include $(BUILD_SHARED_LIBRARY)
diff --git a/jni/com_android_mtp_AppFuse.cpp b/jni/com_android_mtp_AppFuse.cpp
deleted file mode 100644
index e948cf7..0000000
--- a/jni/com_android_mtp_AppFuse.cpp
+++ /dev/null
@@ -1,571 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specic language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "AppFuseJNI"
-#include "utils/Log.h"
-
-#include <assert.h>
-#include <dirent.h>
-#include <inttypes.h>
-
-#include <linux/fuse.h>
-#include <sys/stat.h>
-#include <sys/uio.h>
-
-#include <map>
-
-#include "jni.h"
-#include "JNIHelp.h"
-#include "android_runtime/AndroidRuntime.h"
-#include "nativehelper/ScopedPrimitiveArray.h"
-#include "nativehelper/ScopedLocalRef.h"
-
-namespace {
-
-// The numbers came from sdcard.c.
-// Maximum number of bytes to write/read in one request/one reply.
-constexpr size_t MAX_WRITE = 256 * 1024;
-constexpr size_t MAX_READ = 128 * 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
-// because it has the largest possible data payload.
-constexpr size_t MAX_REQUEST_SIZE = sizeof(struct fuse_in_header) +
- sizeof(struct fuse_write_in) + (MAX_WRITE > MAX_READ ? MAX_WRITE : MAX_READ);
-
-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 jmethodID app_fuse_flush_file_handle;
-static jmethodID app_fuse_close_file_handle;
-static jfieldID app_fuse_buffer;
-
-// NOTE:
-// FuseRequest and FuseResponse shares the same buffer to save memory usage, so the handlers must
-// not access input buffer after writing data to output buffer.
-struct FuseRequest {
- char buffer[MAX_REQUEST_SIZE];
- FuseRequest() {}
- const struct fuse_in_header& header() const {
- return *(const struct fuse_in_header*) buffer;
- }
- void* data() {
- return (buffer + sizeof(struct fuse_in_header));
- }
- size_t data_length() const {
- return header().len - sizeof(struct fuse_in_header);
- }
-};
-
-template<typename T>
-class FuseResponse {
- size_t size_;
- T* const buffer_;
-public:
- explicit FuseResponse(void* buffer) : size_(0), buffer_(static_cast<T*>(buffer)) {}
-
- void prepare_buffer(size_t size = sizeof(T)) {
- memset(buffer_, 0, size);
- size_ = size;
- }
-
- void set_size(size_t size) {
- size_ = size;
- }
-
- size_t size() const { return size_; }
- T* data() const { return buffer_; }
-};
-
-class ScopedFd {
- int mFd;
-
-public:
- explicit ScopedFd(int fd) : mFd(fd) {}
- ~ScopedFd() {
- close(mFd);
- }
- operator int() {
- return mFd;
- }
-};
-
-/**
- * Fuse implementation consists of handlers parsing FUSE commands.
- */
-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) :
- env_(env), self_(self), handle_counter_(0) {}
-
- void handle_fuse_request(int fd, 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;
- case FUSE_FORGET:
- // Return without replying.
- return;
- case FUSE_INIT:
- invoke_handler(fd, req, &AppFuse::handle_fuse_init);
- return;
- case FUSE_GETATTR:
- invoke_handler(fd, req, &AppFuse::handle_fuse_getattr);
- return;
- case FUSE_OPEN:
- invoke_handler(fd, req, &AppFuse::handle_fuse_open);
- return;
- case FUSE_READ:
- invoke_handler(fd, req, &AppFuse::handle_fuse_read);
- return;
- case FUSE_WRITE:
- invoke_handler(fd, req, &AppFuse::handle_fuse_write);
- return;
- case FUSE_RELEASE:
- invoke_handler(fd, req, &AppFuse::handle_fuse_release);
- return;
- case FUSE_FLUSH:
- invoke_handler(fd, req, &AppFuse::handle_fuse_flush);
- return;
- default: {
- ALOGV("NOTIMPL op=%d uniq=%" PRIx64 " nid=%" PRIx64 "\n",
- req->header().opcode,
- req->header().unique,
- req->header().nodeid);
- fuse_reply(fd, req->header().unique, -ENOSYS, NULL, 0);
- return;
- }
- }
- }
-
-private:
- int handle_fuse_lookup(const fuse_in_header& header,
- const char* name,
- FuseResponse<fuse_entry_out>* out) {
- 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->prepare_buffer();
- out->data()->nodeid = n;
- out->data()->attr_valid = 10;
- out->data()->entry_valid = 10;
- out->data()->attr.ino = n;
- out->data()->attr.mode = S_IFREG | 0777;
- out->data()->attr.size = size;
- return 0;
- }
-
- int handle_fuse_init(const fuse_in_header&,
- const fuse_init_in* in,
- FuseResponse<fuse_init_out>* out) {
- // 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
- // new parameters.
- if (in->major != FUSE_KERNEL_VERSION || in->minor < 6) {
- ALOGE("Fuse kernel version mismatch: Kernel version %d.%d, "
- "Expected at least %d.6",
- in->major, in->minor, FUSE_KERNEL_VERSION);
- return -1;
- }
-
- // Before writing |out|, we need to copy data from |in|.
- const uint32_t minor = in->minor;
- const uint32_t max_readahead = in->max_readahead;
-
- // We limit ourselves to 15 because we don't handle BATCH_FORGET yet
- size_t response_size = sizeof(fuse_init_out);
-#if defined(FUSE_COMPAT_22_INIT_OUT_SIZE)
- // FUSE_KERNEL_VERSION >= 23.
-
- // If the kernel only works on minor revs older than or equal to 22,
- // then use the older structure size since this code only uses the 7.22
- // version of the structure.
- if (minor <= 22) {
- response_size = FUSE_COMPAT_22_INIT_OUT_SIZE;
- }
-#endif
- out->prepare_buffer(response_size);
- out->data()->major = FUSE_KERNEL_VERSION;
- out->data()->minor = std::min(minor, 15u);
- out->data()->max_readahead = max_readahead;
- out->data()->flags = FUSE_ATOMIC_O_TRUNC | FUSE_BIG_WRITES;
- out->data()->max_background = 32;
- out->data()->congestion_threshold = 32;
- out->data()->max_write = MAX_WRITE;
-
- return 0;
- }
-
- int handle_fuse_getattr(const fuse_in_header& header,
- const fuse_getattr_in* /* in */,
- FuseResponse<fuse_attr_out>* out) {
- out->prepare_buffer();
- out->data()->attr_valid = 10;
- out->data()->attr.ino = header.nodeid;
- if (header.nodeid == 1) {
- out->data()->attr.mode = S_IFDIR | 0777;
- out->data()->attr.size = 0;
- } else {
- int64_t size = get_file_size(header.nodeid);
- if (size < 0) {
- return -ENOENT;
- }
- out->data()->attr.mode = S_IFREG | 0777;
- out->data()->attr.size = size;
- }
-
- return 0;
- }
-
- int handle_fuse_open(const fuse_in_header& header,
- const fuse_open_in* /* in */,
- FuseResponse<fuse_open_out>* out) {
- 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->prepare_buffer();
- out->data()->fh = handle;
- return 0;
- }
-
- int handle_fuse_read(const fuse_in_header& /* header */,
- const fuse_read_in* in,
- FuseResponse<void>* out) {
- if (in->size > MAX_READ) {
- return -EINVAL;
- }
- const std::map<uint32_t, uint64_t>::iterator it = handles_.find(in->fh);
- if (it == handles_.end()) {
- return -EBADF;
- }
- uint64_t offset = in->offset;
- uint32_t size = in->size;
-
- // Overwrite the size after writing data.
- out->prepare_buffer(0);
- const int64_t result = get_object_bytes(it->second, offset, size, out->data());
- if (result < 0) {
- return result;
- }
- out->set_size(result);
- 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(
- in->fh, 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 */) {
- handles_.erase(in->fh);
- return env_->CallIntMethod(self_, app_fuse_close_file_handle, file_handle_to_jlong(in->fh));
- }
-
- int handle_fuse_flush(const fuse_in_header& /* header */,
- const fuse_flush_in* in,
- FuseResponse<void>* /* out */) {
- return env_->CallIntMethod(self_, app_fuse_flush_file_handle, file_handle_to_jlong(in->fh));
- }
-
- template <typename T, typename S>
- void invoke_handler(int fd,
- FuseRequest* request,
- int (AppFuse::*handler)(const fuse_in_header&,
- const T*,
- FuseResponse<S>*)) {
- FuseResponse<S> response(request->data());
- const int reply_code = (this->*handler)(
- request->header(),
- static_cast<const T*>(request->data()),
- &response);
- fuse_reply(
- fd,
- request->header().unique,
- reply_code,
- request->data(),
- response.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 jlong read_size = env_->CallLongMethod(
- self_,
- app_fuse_read_object_bytes,
- static_cast<jint>(inode),
- static_cast<jlong>(offset),
- static_cast<jlong>(size));
- if (read_size <= 0) {
- return read_size;
- }
- ScopedLocalRef<jbyteArray> array(
- env_, static_cast<jbyteArray>(env_->GetObjectField(self_, app_fuse_buffer)));
- if (array.get() == nullptr) {
- return -EFAULT;
- }
- ScopedByteArrayRO bytes(env_, array.get());
- if (bytes.get() == nullptr) {
- return -ENOMEM;
- }
- memcpy(buf, bytes.get(), read_size);
- return read_size;
- }
-
- int write_object_bytes(uint64_t handle, int inode, uint64_t offset, uint32_t size,
- const void* buffer, uint32_t* written_size) {
- static_assert(sizeof(uint64_t) <= sizeof(jlong),
- "jlong must be able to express any uint64_t values");
- 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);
- }
- const int result = env_->CallIntMethod(
- self_,
- app_fuse_write_object_bytes,
- file_handle_to_jlong(handle),
- inode,
- offset,
- size,
- array.get());
- if (result < 0) {
- return result;
- }
- *written_size = result;
- return 0;
- }
-
- static jlong file_handle_to_jlong(uint64_t handle) {
- static_assert(
- sizeof(uint64_t) <= sizeof(jlong),
- "jlong must be able to express any uint64_t values");
- return static_cast<jlong>(handle);
- }
-
- 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.
- if (reply_code != 0) {
- reply_size = 0;
- }
-
- struct fuse_out_header hdr;
- hdr.len = reply_size + sizeof(hdr);
- hdr.error = reply_code;
- hdr.unique = unique;
-
- struct iovec vec[2];
- vec[0].iov_base = &hdr;
- vec[0].iov_len = sizeof(hdr);
- vec[1].iov_base = reply_data;
- vec[1].iov_len = reply_size;
-
- const int res = writev(fd, vec, reply_size != 0 ? 2 : 1);
- if (res < 0) {
- ALOGE("*** REPLY FAILED *** %d\n", errno);
- }
- }
-};
-
-void com_android_mtp_AppFuse_start_app_fuse_loop(JNIEnv* env, jobject self, jint jfd) {
- ScopedFd fd(static_cast<int>(jfd));
- AppFuse appfuse(env, self);
-
- ALOGV("Start fuse loop.");
- while (true) {
- FuseRequest request;
-
- const ssize_t result = TEMP_FAILURE_RETRY(
- read(fd, request.buffer, sizeof(request.buffer)));
- if (result < 0) {
- if (errno == ENODEV) {
- ALOGV("AppFuse was unmounted.\n");
- return;
- }
- ALOGE("Failed to read bytes from FD: errno=%d\n", errno);
- continue;
- }
-
- const size_t length = static_cast<size_t>(result);
- if (length < sizeof(struct fuse_in_header)) {
- ALOGE("request too short: len=%zu\n", length);
- continue;
- }
-
- if (request.header().len != length) {
- ALOGE("malformed header: len=%zu, hdr->len=%u\n",
- length, request.header().len);
- continue;
- }
-
- appfuse.handle_fuse_request(fd, &request);
- }
-}
-
-static const JNINativeMethod gMethods[] = {
- {
- "native_start_app_fuse_loop",
- "(I)V",
- (void *) com_android_mtp_AppFuse_start_app_fuse_loop
- }
-};
-
-}
-
-jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) {
- JNIEnv* env = nullptr;
- if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
- ALOGE("ERROR: GetEnv failed\n");
- return -1;
-
- }
- assert(env != nullptr);
-
- jclass clazz = env->FindClass("com/android/mtp/AppFuse");
- if (clazz == nullptr) {
- 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_read_object_bytes = env->GetMethodID(
- app_fuse_class, "readObjectBytes", "(IJJ)J");
- if (app_fuse_read_object_bytes == nullptr) {
- ALOGE("Can't find readObjectBytes");
- return -1;
- }
-
- app_fuse_write_object_bytes = env->GetMethodID(app_fuse_class, "writeObjectBytes", "(JIJI[B)I");
- if (app_fuse_write_object_bytes == nullptr) {
- ALOGE("Can't find writeObjectBytes");
- return -1;
- }
-
- app_fuse_flush_file_handle = env->GetMethodID(app_fuse_class, "flushFileHandle", "(J)I");
- if (app_fuse_flush_file_handle == nullptr) {
- ALOGE("Can't find flushFileHandle");
- return -1;
- }
-
- app_fuse_close_file_handle = env->GetMethodID(app_fuse_class, "closeFileHandle", "(J)I");
- if (app_fuse_close_file_handle == nullptr) {
- ALOGE("Can't find closeFileHandle");
- 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) {
- return -1;
- }
-
- return JNI_VERSION_1_4;
-}
diff --git a/perf_tests/src/com/android/mtp/AppFusePerfTest.java b/perf_tests/src/com/android/mtp/AppFusePerfTest.java
index 0762571..36f6fe9 100644
--- a/perf_tests/src/com/android/mtp/AppFusePerfTest.java
+++ b/perf_tests/src/com/android/mtp/AppFusePerfTest.java
@@ -20,6 +20,7 @@
import android.content.Context;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
+import android.os.ProxyFileDescriptorCallback;
import android.os.storage.StorageManager;
import android.system.ErrnoException;
import android.system.Os;
@@ -36,38 +37,13 @@
@RunWith(JUnit4.class)
public class AppFusePerfTest {
+ final static int SIZE = 10 * 1024 * 1024; // 10MB
+
@Test
@LargeTest
public void testReadWriteFile() throws IOException {
final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
final StorageManager storageManager = context.getSystemService(StorageManager.class);
- final int INODE = 10;
- final int SIZE = 10 * 1024 * 1024; // 10MB
- final AppFuse appFuse = new AppFuse(
- "test",
- new TestCallback() {
- @Override
- public long getFileSize(int inode) throws FileNotFoundException {
- if (inode != INODE) {
- throw new FileNotFoundException();
- }
- return SIZE;
- }
-
- @Override
- public long readObjectBytes(int inode, long offset, long size, byte[] bytes)
- throws IOException {
- return size;
- }
-
- @Override
- public int writeObjectBytes(
- long fileHandle, int inode, long offset, int size, byte[] bytes) {
- return size;
- }
- });
-
- appFuse.mount(storageManager);
final byte[] bytes = new byte[SIZE];
final int SAMPLES = 100;
@@ -75,22 +51,20 @@
final double[] writeTime = new double[SAMPLES];
for (int i = 0; i < SAMPLES; i++) {
- final ParcelFileDescriptor fd = appFuse.openFile(
- INODE,
- ParcelFileDescriptor.MODE_READ_ONLY);
+ final ParcelFileDescriptor fd = storageManager.openProxyFileDescriptor(
+ ParcelFileDescriptor.MODE_READ_ONLY, new TestCallback());
try (final ParcelFileDescriptor.AutoCloseInputStream stream =
new ParcelFileDescriptor.AutoCloseInputStream(fd)) {
final long startTime = System.nanoTime();
stream.read(bytes);
readTime[i] = (System.nanoTime() - startTime) / 1000.0 / 1000.0;
}
-
}
for (int i = 0; i < SAMPLES; i++) {
- final ParcelFileDescriptor fd = appFuse.openFile(
- INODE,
- ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_TRUNCATE);
+ final ParcelFileDescriptor fd = storageManager.openProxyFileDescriptor(
+ ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_TRUNCATE,
+ new TestCallback());
try (final ParcelFileDescriptor.AutoCloseOutputStream stream =
new ParcelFileDescriptor.AutoCloseOutputStream(fd)) {
final long startTime = System.nanoTime();
@@ -99,8 +73,6 @@
}
}
- appFuse.close();
-
double readAverage = 0;
double writeAverage = 0;
double readSquaredAverage = 0;
@@ -127,28 +99,26 @@
InstrumentationRegistry.getInstrumentation().sendStatus(Activity.RESULT_OK, results);
}
- private static class TestCallback implements AppFuse.Callback {
+ private static class TestCallback extends ProxyFileDescriptorCallback {
@Override
- public long getFileSize(int inode) throws FileNotFoundException {
- throw new FileNotFoundException();
+ public long onGetSize() throws ErrnoException {
+ return SIZE;
}
@Override
- public long readObjectBytes(int inode, long offset, long size, byte[] bytes)
- throws IOException {
- throw new IOException();
+ public int onRead(long offset, int size, byte[] data) throws ErrnoException {
+ return size;
}
@Override
- public int writeObjectBytes(long fileHandle, int inode, long offset, int size, byte[] bytes)
- throws IOException {
- throw new IOException();
+ public int onWrite(long offset, int size, byte[] data) throws ErrnoException {
+ return size;
}
@Override
- public void flushFileHandle(long fileHandle) throws IOException {}
+ public void onFsync() throws ErrnoException {}
@Override
- public void closeFileHandle(long fileHandle) {}
+ public void onRelease() {}
}
}
diff --git a/src/com/android/mtp/AppFuse.java b/src/com/android/mtp/AppFuse.java
deleted file mode 100644
index cd78e61..0000000
--- a/src/com/android/mtp/AppFuse.java
+++ /dev/null
@@ -1,258 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.mtp;
-
-import android.annotation.WorkerThread;
-import android.os.ParcelFileDescriptor;
-import android.os.Process;
-import android.os.storage.StorageManager;
-import android.system.ErrnoException;
-import android.system.OsConstants;
-import android.util.Log;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.Preconditions;
-import com.android.mtp.annotations.UsedByNative;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-
-public class AppFuse {
- static {
- System.loadLibrary("appfuse_jni");
- }
-
- private static final boolean DEBUG = false;
-
- /**
- * 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;
-
- /**
- * Buffer for read bytes request.
- * Don't use the buffer from the out of AppFuseMessageThread.
- */
- private byte[] mBuffer = new byte[Math.max(MAX_READ, MAX_WRITE)];
-
- private Thread mMessageThread;
- private ParcelFileDescriptor mDeviceFd;
-
- AppFuse(String name, Callback callback) {
- mName = name;
- mCallback = callback;
- }
-
- void mount(StorageManager storageManager) throws IOException {
- Preconditions.checkState(mDeviceFd == null);
- mDeviceFd = storageManager.mountAppFuse(mName);
- mMessageThread = new AppFuseMessageThread(mDeviceFd.dup().detachFd());
- mMessageThread.start();
- }
-
- @VisibleForTesting
- void close() {
- try {
- // Remote side of ParcelFileDescriptor is tracking the close of mDeviceFd, and unmount
- // the corresponding fuse file system. The mMessageThread will receive ENODEV, and
- // then terminate itself.
- mDeviceFd.close();
- mMessageThread.join();
- } catch (IOException exp) {
- Log.e(MtpDocumentsProvider.TAG, "Failed to close device FD.", exp);
- } catch (InterruptedException exp) {
- Log.e(MtpDocumentsProvider.TAG, "Failed to terminate message thread.", exp);
- }
- }
-
- /**
- * 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 |
- ParcelFileDescriptor.MODE_TRUNCATE));
- return ParcelFileDescriptor.open(new File(
- getMountPoint(),
- Integer.toString(i)),
- mode);
- }
-
- File getMountPoint() {
- return new File("/mnt/appfuse/" + Process.myUid() + "_" + mName);
- }
-
- static interface Callback {
- /**
- * Returns file size for the given inode.
- * @param inode
- * @return File size. Must not be negative.
- * @throws FileNotFoundException
- */
- long getFileSize(int inode) throws FileNotFoundException;
-
- /**
- * Returns file 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
- */
- long readObjectBytes(int inode, long offset, long size, byte[] bytes) throws IOException;
-
- /**
- * Handles writing bytes for the give inode.
- * @param fileHandle
- * @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(long fileHandle, int inode, long offset, int size, byte[] bytes)
- throws IOException, ErrnoException;
-
- /**
- * Flushes bytes for file handle.
- * @param fileHandle
- * @throws IOException
- * @throws ErrnoException
- */
- void flushFileHandle(long fileHandle) throws IOException, ErrnoException;
-
- /**
- * Closes file handle.
- * @param fileHandle
- * @throws IOException
- */
- void closeFileHandle(long fileHandle) throws IOException, ErrnoException;
- }
-
- @UsedByNative("com_android_mtp_AppFuse.cpp")
- @WorkerThread
- private long getFileSize(int inode) {
- try {
- return mCallback.getFileSize(inode);
- } catch (Exception error) {
- return -getErrnoFromException(error);
- }
- }
-
- @UsedByNative("com_android_mtp_AppFuse.cpp")
- @WorkerThread
- private long readObjectBytes(int inode, long offset, long size) {
- if (offset < 0 || size < 0 || size > MAX_READ) {
- return -OsConstants.EINVAL;
- }
- try {
- // It's OK to share the same mBuffer among requests because the requests are processed
- // by AppFuseMessageThread sequentially.
- return mCallback.readObjectBytes(inode, offset, size, mBuffer);
- } catch (Exception error) {
- return -getErrnoFromException(error);
- }
- }
-
- @UsedByNative("com_android_mtp_AppFuse.cpp")
- @WorkerThread
- private /* unsgined */ int writeObjectBytes(long fileHandler,
- int inode,
- /* unsigned */ long offset,
- /* unsigned */ int size,
- byte[] bytes) {
- try {
- return mCallback.writeObjectBytes(fileHandler, inode, offset, size, bytes);
- } catch (Exception error) {
- return -getErrnoFromException(error);
- }
- }
-
- @UsedByNative("com_android_mtp_AppFuse.cpp")
- @WorkerThread
- private int flushFileHandle(long fileHandle) {
- try {
- mCallback.flushFileHandle(fileHandle);
- return 0;
- } catch (Exception error) {
- return -getErrnoFromException(error);
- }
- }
-
- @UsedByNative("com_android_mtp_AppFuse.cpp")
- @WorkerThread
- private int closeFileHandle(long fileHandle) {
- try {
- mCallback.closeFileHandle(fileHandle);
- return 0;
- } catch (Exception error) {
- return -getErrnoFromException(error);
- }
- }
-
- private static int getErrnoFromException(Exception error) {
- if (DEBUG) {
- Log.e(MtpDocumentsProvider.TAG, "AppFuse callbacks", error);
- }
- if (error instanceof FileNotFoundException) {
- return OsConstants.ENOENT;
- } else if (error instanceof IOException) {
- return OsConstants.EIO;
- } else if (error instanceof UnsupportedOperationException) {
- return OsConstants.ENOTSUP;
- } else if (error instanceof IllegalArgumentException) {
- return OsConstants.EINVAL;
- } else {
- return OsConstants.EIO;
- }
- }
-
- private native void native_start_app_fuse_loop(int fd);
-
- private class AppFuseMessageThread extends Thread {
- /**
- * File descriptor used by native loop.
- * It's owned by native loop and does not need to close here.
- */
- private final int mRawFd;
-
- AppFuseMessageThread(int fd) {
- super("AppFuseMessageThread");
- mRawFd = fd;
- }
-
- @Override
- public void run() {
- native_start_app_fuse_loop(mRawFd);
- }
- }
-}
diff --git a/src/com/android/mtp/MtpDocumentsProvider.java b/src/com/android/mtp/MtpDocumentsProvider.java
index 6b2c1ee..8b0e610 100644
--- a/src/com/android/mtp/MtpDocumentsProvider.java
+++ b/src/com/android/mtp/MtpDocumentsProvider.java
@@ -33,6 +33,7 @@
import android.os.CancellationSignal;
import android.os.FileUtils;
import android.os.ParcelFileDescriptor;
+import android.os.ProxyFileDescriptorCallback;
import android.os.storage.StorageManager;
import android.provider.DocumentsContract.Document;
import android.provider.DocumentsContract.Path;
@@ -41,6 +42,7 @@
import android.provider.DocumentsProvider;
import android.provider.Settings;
import android.system.ErrnoException;
+import android.system.OsConstants;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
@@ -54,6 +56,8 @@
import java.util.Map;
import java.util.concurrent.TimeoutException;
+import libcore.io.IoUtils;
+
/**
* DocumentsProvider for MTP devices.
*/
@@ -84,9 +88,9 @@
private RootScanner mRootScanner;
private Resources mResources;
private MtpDatabase mDatabase;
- private AppFuse mAppFuse;
private ServiceIntentSender mIntentSender;
private Context mContext;
+ private StorageManager mStorageManager;
/**
* Provides singleton instance to MtpDocumentsService.
@@ -105,8 +109,8 @@
mDeviceToolkits = new HashMap<Integer, DeviceToolkit>();
mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_FILE);
mRootScanner = new RootScanner(mResolver, mMtpManager, mDatabase);
- mAppFuse = new AppFuse(TAG, new AppFuseCallback());
mIntentSender = new ServiceIntentSender(getContext());
+ mStorageManager = getContext().getSystemService(StorageManager.class);
// Check boot count and cleans database if it's first time to launch MtpDocumentsProvider
// after booting.
@@ -129,14 +133,6 @@
return false;
}
- // TODO: Mount AppFuse on demands.
- try {
- mAppFuse.mount(getContext().getSystemService(StorageManager.class));
- } catch (IOException error) {
- Log.e(TAG, "Failed to start app fuse.", error);
- return false;
- }
-
resume();
return true;
}
@@ -157,16 +153,9 @@
mDeviceToolkits = new HashMap<Integer, DeviceToolkit>();
mDatabase = database;
mRootScanner = new RootScanner(mResolver, mMtpManager, mDatabase);
- mAppFuse = new AppFuse(TAG, new AppFuseCallback());
mIntentSender = intentSender;
+ mStorageManager = storageManager;
- // TODO: Mount AppFuse on demands.
- try {
- mAppFuse.mount(storageManager);
- } catch (IOException e) {
- Log.e(TAG, "Failed to start app fuse.", e);
- return false;
- }
resume();
return true;
}
@@ -252,7 +241,10 @@
}
if (MtpDeviceRecord.isPartialReadSupported(
device.operationsSupported, fileSize)) {
- return mAppFuse.openFile(Integer.parseInt(documentId), modeFlag);
+
+ return mStorageManager.openProxyFileDescriptor(
+ modeFlag,
+ new MtpProxyFileDescriptorCallback(Integer.parseInt(documentId)));
} else {
// If getPartialObject{|64} are not supported for the device, returns
// non-seekable pipe FD instead.
@@ -262,7 +254,9 @@
// TODO: Clear the parent document loader task (if exists) and call notify
// when writing is completed.
if (MtpDeviceRecord.isWritingSupported(device.operationsSupported)) {
- return mAppFuse.openFile(Integer.parseInt(documentId), modeFlag);
+ return mStorageManager.openProxyFileDescriptor(
+ modeFlag,
+ new MtpProxyFileDescriptorCallback(Integer.parseInt(documentId)));
} else {
throw new UnsupportedOperationException(
"The device does not support writing operation.");
@@ -586,7 +580,6 @@
throw new RuntimeException(e);
} finally {
mDatabase.close();
- mAppFuse.close();
super.shutdown();
}
}
@@ -693,72 +686,92 @@
}
}
- private class AppFuseCallback implements AppFuse.Callback {
- private final Map<Long, MtpFileWriter> mWriters = new HashMap<>();
+ private class MtpProxyFileDescriptorCallback extends ProxyFileDescriptorCallback {
+ private final int mInode;
+ private MtpFileWriter mWriter;
- @Override
- public long getFileSize(int inode) throws FileNotFoundException {
- return MtpDocumentsProvider.this.getFileSize(String.valueOf(inode));
+ MtpProxyFileDescriptorCallback(int inode) {
+ mInode = inode;
}
@Override
- public long readObjectBytes(
- int inode, long offset, long size, byte[] buffer) throws IOException {
- final Identifier identifier = mDatabase.createIdentifier(Integer.toString(inode));
- final MtpDeviceRecord record = getDeviceToolkit(identifier.mDeviceId).mDeviceRecord;
-
- if (MtpDeviceRecord.isSupported(
- record.operationsSupported, MtpConstants.OPERATION_GET_PARTIAL_OBJECT_64)) {
- return mMtpManager.getPartialObject64(
- identifier.mDeviceId, identifier.mObjectHandle, offset, size, buffer);
- }
-
- if (0 <= offset && offset <= 0xffffffffL && MtpDeviceRecord.isSupported(
- record.operationsSupported, MtpConstants.OPERATION_GET_PARTIAL_OBJECT)) {
- return mMtpManager.getPartialObject(
- identifier.mDeviceId, identifier.mObjectHandle, offset, size, buffer);
- }
-
- throw new UnsupportedOperationException();
- }
-
- @Override
- public int writeObjectBytes(
- long fileHandle, int inode, long offset, int size, byte[] bytes)
- throws IOException, ErrnoException {
- final MtpFileWriter writer;
- if (mWriters.containsKey(fileHandle)) {
- writer = mWriters.get(fileHandle);
- } else {
- writer = new MtpFileWriter(mContext, String.valueOf(inode));
- mWriters.put(fileHandle, writer);
- }
- return writer.write(offset, size, bytes);
- }
-
- @Override
- public void flushFileHandle(long fileHandle) throws IOException, ErrnoException {
- final MtpFileWriter writer = mWriters.get(fileHandle);
- if (writer == null) {
- // File handle for reading.
- return;
- }
- final MtpDeviceRecord device = getDeviceToolkit(
- mDatabase.createIdentifier(writer.getDocumentId()).mDeviceId).mDeviceRecord;
- writer.flush(mMtpManager, mDatabase, device.operationsSupported);
- }
-
- @Override
- public void closeFileHandle(long fileHandle) throws IOException, ErrnoException {
- final MtpFileWriter writer = mWriters.get(fileHandle);
- if (writer == null) {
- // File handle for reading.
- return;
- }
+ public long onGetSize() throws ErrnoException {
try {
- writer.close();
+ return getFileSize(String.valueOf(mInode));
+ } catch (FileNotFoundException e) {
+ Log.e(TAG, e.getMessage(), e);
+ throw new ErrnoException("onGetSize", OsConstants.ENOENT);
+ }
+ }
+
+ @Override
+ public int onRead(long offset, int size, byte[] data) throws ErrnoException {
+ try {
+ final Identifier identifier = mDatabase.createIdentifier(Integer.toString(mInode));
+ final MtpDeviceRecord record = getDeviceToolkit(identifier.mDeviceId).mDeviceRecord;
+ if (MtpDeviceRecord.isSupported(
+ record.operationsSupported, MtpConstants.OPERATION_GET_PARTIAL_OBJECT_64)) {
+
+ return (int) mMtpManager.getPartialObject64(
+ identifier.mDeviceId, identifier.mObjectHandle, offset, size, data);
+
+ }
+ if (0 <= offset && offset <= 0xffffffffL && MtpDeviceRecord.isSupported(
+ record.operationsSupported, MtpConstants.OPERATION_GET_PARTIAL_OBJECT)) {
+ return (int) mMtpManager.getPartialObject(
+ identifier.mDeviceId, identifier.mObjectHandle, offset, size, data);
+ }
+ throw new ErrnoException("onRead", OsConstants.ENOTSUP);
+ } catch (IOException e) {
+ Log.e(TAG, e.getMessage(), e);
+ throw new ErrnoException("onRead", OsConstants.EIO);
+ }
+ }
+
+ @Override
+ public int onWrite(long offset, int size, byte[] data) throws ErrnoException {
+ try {
+ if (mWriter == null) {
+ mWriter = new MtpFileWriter(mContext, String.valueOf(mInode));
+ }
+ return mWriter.write(offset, size, data);
+ } catch (IOException e) {
+ Log.e(TAG, e.getMessage(), e);
+ throw new ErrnoException("onWrite", OsConstants.EIO);
+ }
+ }
+
+ @Override
+ public void onFsync() throws ErrnoException {
+ tryFsync();
+ }
+
+ @Override
+ public void onRelease() {
+ try {
+ tryFsync();
+ } catch (ErrnoException error) {
+ // Cannot recover from the error at onRelease. Client app should use fsync to
+ // ensure the provider writes data correctly.
+ Log.e(TAG, "Cannot recover from the error at onRelease.", error);
} finally {
- mWriters.remove(fileHandle);
+ if (mWriter != null) {
+ IoUtils.closeQuietly(mWriter);
+ }
+ }
+ }
+
+ private void tryFsync() throws ErrnoException {
+ try {
+ if (mWriter != null) {
+ final MtpDeviceRecord device =
+ getDeviceToolkit(mDatabase.createIdentifier(
+ mWriter.getDocumentId()).mDeviceId).mDeviceRecord;
+ mWriter.flush(mMtpManager, mDatabase, device.operationsSupported);
+ }
+ } catch (IOException e) {
+ Log.e(TAG, e.getMessage(), e);
+ throw new ErrnoException("onWrite", OsConstants.EIO);
}
}
}
diff --git a/tests/src/com/android/mtp/AppFuseTest.java b/tests/src/com/android/mtp/AppFuseTest.java
deleted file mode 100644
index e421de7..0000000
--- a/tests/src/com/android/mtp/AppFuseTest.java
+++ /dev/null
@@ -1,262 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-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.MediumTest;
-
-import libcore.io.IoUtils;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.util.Arrays;
-
-@MediumTest
-public class AppFuseTest extends AndroidTestCase {
- public void testMount() throws ErrnoException, IOException {
- final StorageManager storageManager = getContext().getSystemService(StorageManager.class);
- final AppFuse appFuse = new AppFuse("test", new TestCallback());
- appFuse.mount(storageManager);
- final File file = appFuse.getMountPoint();
- assertTrue(file.isDirectory());
- assertEquals(1, Os.stat(file.getPath()).st_ino);
- appFuse.close();
- assertTrue(1 != Os.stat(file.getPath()).st_ino);
- }
-
- public void 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, ParcelFileDescriptor.MODE_READ_ONLY);
- fd.close();
- appFuse.close();
- }
-
- public void testOpenFile_fileNotFound() 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_ONLY);
- fail();
- } 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();
- }
-
- public void testReadFile() throws IOException {
- final StorageManager storageManager = getContext().getSystemService(StorageManager.class);
- final int fileInode = 10;
- final byte[] fileBytes = 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 == fileInode) {
- return fileBytes.length;
- }
- return super.getFileSize(inode);
- }
-
- @Override
- public long readObjectBytes(int inode, long offset, long size, byte[] bytes)
- throws IOException {
- if (inode == fileInode) {
- int i = 0;
- while (i < size && i + offset < fileBytes.length) {
- bytes[i] = fileBytes[(int) (i + offset)];
- i++;
- }
- return i;
- }
- return super.readObjectBytes(inode, offset, size, bytes);
- }
- });
- appFuse.mount(storageManager);
- 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];
- final int size = stream.read(buffer, 0, buffer.length);
- assertEquals(5, size);
- }
- 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(
- long fileHandle, 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 | ParcelFileDescriptor.MODE_TRUNCATE);
- 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 | ParcelFileDescriptor.MODE_TRUNCATE);
- try (final ParcelFileDescriptor.AutoCloseOutputStream stream =
- new ParcelFileDescriptor.AutoCloseOutputStream(fd)) {
- stream.write('a');
- fail();
- } catch (IOException e) {
- }
- appFuse.close();
- }
-
- public void testWriteFile_flushError() 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;
- }
-
- @Override
- public int writeObjectBytes(
- long fileHandle, int inode, long offset, int size, byte[] bytes) {
- return size;
- }
-
- @Override
- public void flushFileHandle(long fileHandle) throws IOException {
- throw new IOException();
- }
- });
- appFuse.mount(storageManager);
- final ParcelFileDescriptor fd = appFuse.openFile(
- INODE, ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_TRUNCATE);
- try (final ParcelFileDescriptor.AutoCloseOutputStream stream =
- new ParcelFileDescriptor.AutoCloseOutputStream(fd)) {
- stream.write('a');
- try {
- IoUtils.close(fd.getFileDescriptor());
- fail();
- } catch (IOException e) {
- }
- }
- appFuse.close();
- }
-
- private static class TestCallback implements AppFuse.Callback {
- @Override
- public long getFileSize(int inode) throws FileNotFoundException {
- throw new FileNotFoundException();
- }
-
- @Override
- public long readObjectBytes(int inode, long offset, long size, byte[] bytes)
- throws IOException {
- throw new IOException();
- }
-
- @Override
- public int writeObjectBytes(long fileHandle, int inode, long offset, int size, byte[] bytes)
- throws IOException {
- throw new IOException();
- }
-
- @Override
- public void flushFileHandle(long fileHandle) throws IOException {}
-
- @Override
- public void closeFileHandle(long fileHandle) {}
- }
-}
diff --git a/tests/src/com/android/mtp/MtpDocumentsProviderTest.java b/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
index a9d35e1..491e24d 100644
--- a/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
+++ b/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
@@ -557,6 +557,7 @@
try (ParcelFileDescriptor.AutoCloseOutputStream stream =
new ParcelFileDescriptor.AutoCloseOutputStream(fd)) {
stream.write("Hello".getBytes());
+ fd.getFileDescriptor().sync();
}
}
{