Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b89bb9c8 authored by Daichi Hirono's avatar Daichi Hirono Committed by android-build-merger
Browse files

Merge "Use AppFuse to write document." into nyc-dev

am: c1244278

* commit 'c1244278':
  Use AppFuse to write document.

Change-Id: I36769d0ec7fbe73f4eb3aec66513a2806c1b5c5e
parents 7709c892 c1244278
Loading
Loading
Loading
Loading
+5 −1
Original line number Diff line number Diff line
@@ -45,6 +45,8 @@ import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Document;
import android.system.ErrnoException;
import android.system.Os;
import android.text.format.DateUtils;
import android.util.Log;
import android.webkit.MimeTypeMap;
@@ -451,7 +453,7 @@ class CopyJob extends Job {
        ParcelFileDescriptor srcFile = null;
        ParcelFileDescriptor dstFile = null;
        InputStream in = null;
        OutputStream out = null;
        ParcelFileDescriptor.AutoCloseOutputStream out = null;
        boolean success = false;

        try {
@@ -502,6 +504,8 @@ class CopyJob extends Job {
                    makeCopyProgress(len);
                }

                // Need to invoke IoUtils.close explicitly to avoid from ignoring errors at flush.
                IoUtils.close(dstFile.getFileDescriptor());
                srcFile.checkError();
            } catch (IOException e) {
                throw new ResourceException(
+41 −17
Original line number Diff line number Diff line
@@ -52,6 +52,8 @@ 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:
@@ -307,7 +309,8 @@ private:
        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);
        const int result = write_object_bytes(
                in->fh, it->second, offset, size, buffer, &written_size);
        if (result < 0) {
            return result;
        }
@@ -320,13 +323,13 @@ private:
                            const fuse_release_in* in,
                            FuseResponse<void>* /* out */) {
        handles_.erase(in->fh);
        return 0;
        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 void* /* in */,
                          const fuse_flush_in* in,
                          FuseResponse<void>* /* out */) {
        return 0;
        return env_->CallIntMethod(self_, app_fuse_flush_file_handle, file_handle_to_jlong(in->fh));
    }

    template <typename T, typename S>
@@ -382,8 +385,10 @@ private:
        return read_size;
    }

    int write_object_bytes(int inode, uint64_t offset, uint32_t size, const void* buffer,
                           uint32_t* written_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)));
@@ -394,15 +399,28 @@ private:
            }
            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;
        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.
@@ -511,15 +529,21 @@ jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) {
        return -1;
    }

    app_fuse_buffer = env->GetFieldID(app_fuse_class, "mBuffer", "[B");
    if (app_fuse_buffer == nullptr) {
        ALOGE("Can't find mBuffer");
    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_write_object_bytes = env->GetMethodID(app_fuse_class, "writeObjectBytes", "(IJI[B)I");
    if (app_fuse_write_object_bytes == nullptr) {
        ALOGE("Can't find getWriteObjectBytes");
    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;
    }

+74 −13
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ 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;
@@ -34,6 +35,8 @@ public class AppFuse {
        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.
@@ -94,7 +97,8 @@ public class AppFuse {
    public ParcelFileDescriptor openFile(int i, int mode) throws FileNotFoundException {
        Preconditions.checkArgument(
                mode == ParcelFileDescriptor.MODE_READ_ONLY ||
                mode == ParcelFileDescriptor.MODE_WRITE_ONLY);
                mode == (ParcelFileDescriptor.MODE_WRITE_ONLY |
                         ParcelFileDescriptor.MODE_TRUNCATE));
        return ParcelFileDescriptor.open(new File(
                getMountPoint(),
                Integer.toString(i)),
@@ -127,6 +131,7 @@ public class AppFuse {

        /**
         * Handles writing bytes for the give inode.
         * @param fileHandle
         * @param inode
         * @param offset Offset for file bytes.
         * @param size Size for file bytes.
@@ -134,7 +139,23 @@ public class AppFuse {
         * @return Number of read bytes. Must not be negative.
         * @throws IOException
         */
        int writeObjectBytes(int inode, long offset, int size, byte[] bytes) 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")
@@ -142,10 +163,8 @@ public class AppFuse {
    private long getFileSize(int inode) {
        try {
            return mCallback.getFileSize(inode);
        } catch (FileNotFoundException e) {
            return -OsConstants.ENOENT;
        } catch (UnsupportedOperationException e) {
            return -OsConstants.ENOTSUP;
        } catch (Exception error) {
            return -getErrnoFromException(error);
        }
    }

@@ -159,20 +178,62 @@ public class AppFuse {
            // 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 (IOException e) {
            return -OsConstants.EIO;
        } catch (UnsupportedOperationException e) {
            return -OsConstants.ENOTSUP;
        } catch (Exception error) {
            return -getErrnoFromException(error);
        }
    }

    @UsedByNative("com_android_mtp_AppFuse.cpp")
    @WorkerThread
    private /* unsgined */ int writeObjectBytes(int inode,
    private /* unsgined */ int writeObjectBytes(long fileHandler,
                                                int inode,
                                                /* unsigned */ long offset,
                                                /* unsigned */ int size,
                                                byte[] bytes) throws IOException {
        return mCallback.writeObjectBytes(inode, offset, size, bytes);
                                                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 boolean native_start_app_fuse_loop(int fd);
+84 −39
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.mtp;

import android.content.ContentResolver;
import android.content.Context;
import android.content.UriPermission;
import android.content.res.AssetFileDescriptor;
import android.content.res.Resources;
@@ -38,11 +39,16 @@ import android.provider.DocumentsContract.Root;
import android.provider.DocumentsContract;
import android.provider.DocumentsProvider;
import android.provider.Settings;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
import android.util.Log;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;

import java.io.File;
import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.HashMap;
@@ -82,6 +88,7 @@ public class MtpDocumentsProvider extends DocumentsProvider {
    private MtpDatabase mDatabase;
    private AppFuse mAppFuse;
    private ServiceIntentSender mIntentSender;
    private Context mContext;

    /**
     * Provides singleton instance to MtpDocumentsService.
@@ -93,6 +100,7 @@ public class MtpDocumentsProvider extends DocumentsProvider {
    @Override
    public boolean onCreate() {
        sSingleton = this;
        mContext = getContext();
        mResources = getContext().getResources();
        mMtpManager = new MtpManager(getContext());
        mResolver = getContext().getContentResolver();
@@ -137,12 +145,14 @@ public class MtpDocumentsProvider extends DocumentsProvider {

    @VisibleForTesting
    boolean onCreateForTesting(
            Context context,
            Resources resources,
            MtpManager mtpManager,
            ContentResolver resolver,
            MtpDatabase database,
            StorageManager storageManager,
            ServiceIntentSender intentSender) {
        mContext = context;
        mResources = resources;
        mMtpManager = mtpManager;
        mResolver = resolver;
@@ -232,43 +242,43 @@ public class MtpDocumentsProvider extends DocumentsProvider {
        try {
            openDevice(identifier.mDeviceId);
            final MtpDeviceRecord device = getDeviceToolkit(identifier.mDeviceId).mDeviceRecord;
            switch (mode) {
                case "r":
            // Turn off MODE_CREATE because openDocument does not allow to create new files.
            final int modeFlag =
                    ParcelFileDescriptor.parseMode(mode) & ~ParcelFileDescriptor.MODE_CREATE;
            if ((modeFlag & ParcelFileDescriptor.MODE_READ_ONLY) != 0) {
                long fileSize;
                try {
                    fileSize = getFileSize(documentId);
                } catch (UnsupportedOperationException exception) {
                    fileSize = -1;
                }
                    // MTP getPartialObject operation does not support files that are larger than
                    // 4GB. Fallback to non-seekable file descriptor.
                if (MtpDeviceRecord.isPartialReadSupported(
                        device.operationsSupported, fileSize)) {
                        return mAppFuse.openFile(
                                Integer.parseInt(documentId), ParcelFileDescriptor.MODE_READ_ONLY);
                    return mAppFuse.openFile(Integer.parseInt(documentId), modeFlag);
                } else {
                    // If getPartialObject{|64} are not supported for the device, returns
                    // non-seekable pipe FD instead.
                    return getPipeManager(identifier).readDocument(mMtpManager, identifier);
                }
                case "w":
            } else if ((modeFlag & ParcelFileDescriptor.MODE_WRITE_ONLY) != 0) {
                // TODO: Clear the parent document loader task (if exists) and call notify
                // when writing is completed.
                if (MtpDeviceRecord.isWritingSupported(device.operationsSupported)) {
                        return getPipeManager(identifier).writeDocument(
                                getContext(), mMtpManager, identifier, device.operationsSupported);
                    return mAppFuse.openFile(Integer.parseInt(documentId), modeFlag);
                } else {
                    throw new UnsupportedOperationException(
                            "The device does not support writing operation.");
                }
                case "rw":
            } else {
                // TODO: Add support for "rw" mode.
                    throw new UnsupportedOperationException(
                            "The provider does not support 'rw' mode.");
                default:
                    throw new IllegalArgumentException("Unknown mode for openDocument: " + mode);
                throw new UnsupportedOperationException("The provider does not support 'rw' mode.");
            }
        } catch (FileNotFoundException | RuntimeException error) {
            Log.e(MtpDocumentsProvider.TAG, "openDocument", error);
            throw error;
        } catch (IOException error) {
            Log.e(MtpDocumentsProvider.TAG, "openDocument", error);
            throw new FileNotFoundException(error.getMessage());
            throw new IllegalStateException(error);
        }
    }

@@ -595,6 +605,13 @@ public class MtpDocumentsProvider extends DocumentsProvider {
    }

    private class AppFuseCallback implements AppFuse.Callback {
        private final Map<Long, MtpFileWriter> mWriters = new HashMap<>();

        @Override
        public long getFileSize(int inode) throws FileNotFoundException {
            return MtpDocumentsProvider.this.getFileSize(String.valueOf(inode));
        }

        @Override
        public long readObjectBytes(
                int inode, long offset, long size, byte[] buffer) throws IOException {
@@ -617,15 +634,43 @@ public class MtpDocumentsProvider extends DocumentsProvider {
        }

        @Override
        public long getFileSize(int inode) throws FileNotFoundException {
            return MtpDocumentsProvider.this.getFileSize(String.valueOf(inode));
        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 int writeObjectBytes(int inode, long offset, int size, byte[] bytes)
                throws IOException {
            // TODO: Implement it.
            throw new IOException();
        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;
            }
            try {
                writer.close();
            } finally {
                mWriters.remove(fileHandle);
            }
        }
    }
}
+108 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2016 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.content.Context;
import android.mtp.MtpObjectInfo;
import android.os.ParcelFileDescriptor;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;

import com.android.internal.util.Preconditions;

import java.io.File;
import java.io.IOException;

class MtpFileWriter implements AutoCloseable {
    final ParcelFileDescriptor mCacheFd;
    final String mDocumentId;
    boolean mDirty;

    MtpFileWriter(Context context, String documentId) throws IOException {
        mDocumentId = documentId;
        mDirty = false;
        final File tempFile = File.createTempFile("mtp", "tmp", context.getCacheDir());
        mCacheFd = ParcelFileDescriptor.open(
                tempFile,
                ParcelFileDescriptor.MODE_READ_WRITE |
                ParcelFileDescriptor.MODE_TRUNCATE |
                ParcelFileDescriptor.MODE_CREATE);
        tempFile.delete();
    }

    String getDocumentId() {
        return mDocumentId;
    }

    int write(long offset, int size, byte[] bytes) throws IOException, ErrnoException {
        Preconditions.checkArgumentNonnegative(offset, "offset");
        Preconditions.checkArgumentNonnegative(size, "size");
        Preconditions.checkArgument(size <= bytes.length);
        if (size == 0) {
            return 0;
        }
        mDirty = true;
        Os.lseek(mCacheFd.getFileDescriptor(), offset, OsConstants.SEEK_SET);
        return Os.write(mCacheFd.getFileDescriptor(), bytes, 0, size);
    }

    void flush(MtpManager manager, MtpDatabase database, int[] operationsSupported)
            throws IOException, ErrnoException {
        // Skip unnecessary flush.
        if (!mDirty) {
            return;
        }

        // Get the placeholder object info.
        final Identifier identifier = database.createIdentifier(mDocumentId);
        final MtpObjectInfo placeholderObjectInfo =
                manager.getObjectInfo(identifier.mDeviceId, identifier.mObjectHandle);

        // Delete the target object info if it already exists (as a placeholder).
        manager.deleteDocument(identifier.mDeviceId, identifier.mObjectHandle);

        // Create the target object info with a correct file size and upload the file.
        final long size = Os.lseek(mCacheFd.getFileDescriptor(), 0, OsConstants.SEEK_END);
        final MtpObjectInfo targetObjectInfo = new MtpObjectInfo.Builder(placeholderObjectInfo)
                .setCompressedSize(size)
                .build();

        Os.lseek(mCacheFd.getFileDescriptor(), 0, OsConstants.SEEK_SET);
        final int newObjectHandle = manager.createDocument(
                identifier.mDeviceId, targetObjectInfo, mCacheFd);

        final MtpObjectInfo newObjectInfo = manager.getObjectInfo(
                identifier.mDeviceId, newObjectHandle);
        final Identifier parentIdentifier =
                database.getParentIdentifier(identifier.mDocumentId);
        database.updateObject(
                identifier.mDocumentId,
                identifier.mDeviceId,
                parentIdentifier.mDocumentId,
                operationsSupported,
                newObjectInfo,
                size);

        mDirty = false;
    }

    @Override
    public void close() throws IOException {
        mCacheFd.close();
    }
}
Loading