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

Commit 878e86f3 authored by Daichi Hirono's avatar Daichi Hirono
Browse files

Adding FUSE loop thread in app side.

The CL adds FuseAppLoop internal class to the framework. It parses FUSE commands
from the proxy in the system service and invokes a callback provided by
application.

Bug: 29970149
Test: None, will be tested with CTS for StorageManager#openProxyFileDescriptor.
Change-Id: I10b2add4c2743fb91eae3cb194f55843a74fb668
parent d0835e45
Loading
Loading
Loading
Loading
+59 −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 android.os;

import android.system.ErrnoException;

/**
 * Callback that handles file system requests from ProxyFileDescriptor.
 * @hide
 */
public interface IProxyFileDescriptorCallback {
    /**
     * Returns size of bytes provided by the file descriptor.
     * @return Size of bytes
     * @throws ErrnoException
     */
    long onGetSize() throws ErrnoException;

    /**
     * Provides bytes read from file descriptor.
     * It needs to return exact requested size of bytes unless it reaches file end.
     * @param offset Where to read bytes from.
     * @param size Size for read bytes.
     * @param data Byte array to store read bytes.
     * @return Size of bytes returned by the function.
     * @throws ErrnoException
     */
    int onRead(long offset, int size, byte[] data) throws ErrnoException;

    /**
     * Handles bytes written to file descriptor.
     * @param offset Where to write bytes to.
     * @param size Size for write bytes.
     * @param data Byte array to be written to somewhere.
     * @return Size of bytes processed by the function.
     * @throws ErrnoException
     */
    int onWrite(long offset, int size, byte[] data) throws ErrnoException;

    /**
     * Processes fsync request.
     * @throws ErrnoException
     */
    void onFsync() throws ErrnoException;
}
+222 −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.internal.os;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.IProxyFileDescriptorCallback;
import android.os.ParcelFileDescriptor;
import android.system.ErrnoException;
import android.system.OsConstants;
import android.util.Log;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;

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

public class FuseAppLoop {
    private static final String TAG = "FuseAppLoop";
    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
    public static final int ROOT_INODE = 1;
    private static final int MIN_INODE = 2;

    private final Object mLock = new Object();
    private final File mParent;

    @GuardedBy("mLock")
    private final SparseArray<CallbackEntry> mCallbackMap = new SparseArray<>();

    @GuardedBy("mLock")
    private boolean mActive = true;

    /**
     * Sequential number can be used as file name and inode in AppFuse.
     * 0 is regarded as an error, 1 is mount point. So we start the number from 2.
     */
    @GuardedBy("mLock")
    private int mNextInode = MIN_INODE;

    private FuseAppLoop(@NonNull File parent) {
        mParent = parent;
    }

    public static @NonNull FuseAppLoop open(
            @NonNull File parent, @NonNull ParcelFileDescriptor fd) {
        Preconditions.checkNotNull(parent);
        Preconditions.checkNotNull(fd);
        final FuseAppLoop bridge = new FuseAppLoop(parent);
        final int rawFd = fd.detachFd();
        new Thread(new Runnable() {
            @Override
            public void run() {
                bridge.native_start_loop(rawFd);
            }
        }, TAG).start();
        return bridge;
    }

    public @NonNull ParcelFileDescriptor openFile(int mode, IProxyFileDescriptorCallback callback)
            throws UnmountedException, IOException {
        int id;
        synchronized (mLock) {
            if (!mActive) {
                throw new UnmountedException();
            }
            if (mCallbackMap.size() >= Integer.MAX_VALUE - MIN_INODE) {
                throw new IOException("Too many opened files.");
            }
            while (true) {
                id = mNextInode;
                mNextInode++;
                if (mNextInode < 0) {
                    mNextInode = MIN_INODE;
                }
                if (mCallbackMap.get(id) == null) {
                    break;
                }
            }

            // Register callback after we succeed to create pfd.
            mCallbackMap.put(id, new CallbackEntry(callback));
        }
        try {
            return ParcelFileDescriptor.open(new File(mParent, String.valueOf(id)), mode);
        } catch (FileNotFoundException error) {
            synchronized (mLock) {
                mCallbackMap.remove(id);
            }
            throw error;
        }
    }

    public @Nullable File getMountPoint() {
        synchronized (mLock) {
            return mActive ? mParent : null;
        }
    }

    private CallbackEntry getCallbackEntryOrThrowLocked(long inode) throws ErrnoException {
        final CallbackEntry entry = mCallbackMap.get(checkInode(inode));
        if (entry != null) {
            return entry;
        } else {
            throw new ErrnoException("getCallbackEntry", OsConstants.ENOENT);
        }
    }

    // Called by JNI.
    @SuppressWarnings("unused")
    private long onGetSize(long inode) {
        synchronized(mLock) {
            try {
                return getCallbackEntryOrThrowLocked(inode).callback.onGetSize();
            } catch (ErrnoException exp) {
                return -exp.errno;
            }
        }
    }

    // Called by JNI.
    @SuppressWarnings("unused")
    private int onOpen(long inode) {
        synchronized(mLock) {
            try {
                final CallbackEntry entry = getCallbackEntryOrThrowLocked(inode);
                if (entry.opened) {
                    throw new ErrnoException("onOpen", OsConstants.EMFILE);
                }
                entry.opened = true;
                // Use inode as file handle. It's OK because AppFuse does not allow to open the same
                // file twice.
                return (int) inode;
            } catch (ErrnoException exp) {
                return -exp.errno;
            }
        }
    }

    // Called by JNI.
    @SuppressWarnings("unused")
    private int onFsync(long inode) {
        synchronized(mLock) {
            try {
                getCallbackEntryOrThrowLocked(inode).callback.onFsync();
                return 0;
            } catch (ErrnoException exp) {
                return -exp.errno;
            }
        }
    }

    // Called by JNI.
    @SuppressWarnings("unused")
    private int onRelease(long inode) {
        synchronized(mLock) {
            mCallbackMap.remove(checkInode(inode));
            if (mCallbackMap.size() == 0) {
                mActive = false;
                return -1;
            }
            return 0;
        }
    }

    // Called by JNI.
    @SuppressWarnings("unused")
    private int onRead(long inode, long offset, int size, byte[] bytes) {
        synchronized(mLock) {
            try {
                return getCallbackEntryOrThrowLocked(inode).callback.onRead(offset, size, bytes);
            } catch (ErrnoException exp) {
                return -exp.errno;
            }
        }
    }

    // Called by JNI.
    @SuppressWarnings("unused")
    private int onWrite(long inode, long offset, int size, byte[] bytes) {
        synchronized(mLock) {
            try {
                return getCallbackEntryOrThrowLocked(inode).callback.onWrite(offset, size, bytes);
            } catch (ErrnoException exp) {
                return -exp.errno;
            }
        }
    }

    native boolean native_start_loop(int fd);

    private static int checkInode(long inode) {
        Preconditions.checkArgumentInRange(inode, MIN_INODE, Integer.MAX_VALUE, "checkInode");
        return (int) inode;
    }

    public static class UnmountedException extends Exception {}

    private static class CallbackEntry {
        final IProxyFileDescriptorCallback callback;
        boolean opened;
        CallbackEntry(IProxyFileDescriptorCallback callback) {
            Preconditions.checkNotNull(callback);
            this.callback = callback;
        }
    }
}
+3 −0
Original line number Diff line number Diff line
@@ -183,6 +183,7 @@ LOCAL_SRC_FILES:= \
    android_content_res_Configuration.cpp \
    android_animation_PropertyValuesHolder.cpp \
    com_android_internal_net_NetworkStatsFactory.cpp \
    com_android_internal_os_FuseAppLoop.cpp \
    com_android_internal_os_PathClassLoaderFactory.cpp \
    com_android_internal_os_Zygote.cpp \
    com_android_internal_util_VirtualRefBasePtr.cpp \
@@ -201,6 +202,7 @@ LOCAL_C_INCLUDES += \
    $(TOP)/frameworks/base/media/jni \
    $(TOP)/system/core/base/include \
    $(TOP)/system/core/include \
    $(TOP)/system/core/libappfuse/include \
    $(TOP)/system/media/camera/include \
    $(TOP)/system/netd/include \
    external/giflib \
@@ -230,6 +232,7 @@ LOCAL_STATIC_LIBRARIES := \
LOCAL_SHARED_LIBRARIES := \
    libmemtrack \
    libandroidfw \
    libappfuse \
    libbase \
    libexpat \
    libnativehelper \
+2 −1
Original line number Diff line number Diff line
@@ -201,6 +201,7 @@ extern int register_android_content_res_Configuration(JNIEnv* env);
extern int register_android_animation_PropertyValuesHolder(JNIEnv *env);
extern int register_com_android_internal_content_NativeLibraryHelper(JNIEnv *env);
extern int register_com_android_internal_net_NetworkStatsFactory(JNIEnv *env);
extern int register_com_android_internal_os_FuseAppLoop(JNIEnv* env);
extern int register_com_android_internal_os_PathClassLoaderFactory(JNIEnv* env);
extern int register_com_android_internal_os_Zygote(JNIEnv *env);
extern int register_com_android_internal_util_VirtualRefBasePtr(JNIEnv *env);
@@ -1419,7 +1420,7 @@ static const RegJNIRec gRegJNI[] = {
    REG_JNI(register_android_animation_PropertyValuesHolder),
    REG_JNI(register_com_android_internal_content_NativeLibraryHelper),
    REG_JNI(register_com_android_internal_net_NetworkStatsFactory),

    REG_JNI(register_com_android_internal_os_FuseAppLoop),
};

/*
+164 −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.
 */

#define LOG_TAG "FuseAppLoopJNI"
#define LOG_NDEBUG 0

#include <stdlib.h>
#include <sys/stat.h>

#include <android_runtime/Log.h>
#include <android-base/logging.h>
#include <android-base/unique_fd.h>
#include <jni.h>
#include <libappfuse/FuseAppLoop.h>
#include <nativehelper/ScopedLocalRef.h>

#include "core_jni_helpers.h"

namespace android {

namespace {

constexpr const char* CLASS_NAME = "com/android/internal/os/FuseAppLoop";

jclass gFuseAppLoopClass;
jmethodID gOnGetSizeMethod;
jmethodID gOnOpenMethod;
jmethodID gOnFsyncMethod;
jmethodID gOnReleaseMethod;
jmethodID gOnReadMethod;
jmethodID gOnWriteMethod;

class Callback : public fuse::FuseAppLoopCallback {
private:
    static constexpr size_t kBufferSize = std::max(fuse::kFuseMaxWrite, fuse::kFuseMaxRead);
    static_assert(kBufferSize <= INT32_MAX, "kBufferSize should be fit in int32_t.");

    JNIEnv* const mEnv;
    jobject const mSelf;
    ScopedLocalRef<jbyteArray> mJniBuffer;
    bool mActive;

    template <typename T>
    T checkException(T result) const {
        if (mEnv->ExceptionCheck()) {
            LOGE_EX(mEnv, nullptr);
            mEnv->ExceptionClear();
            return -EIO;
        }
        return result;
    }

public:
    Callback(JNIEnv* env, jobject self) :
        mEnv(env),
        mSelf(self),
        mJniBuffer(env, nullptr),
        mActive(true) {}

    bool Init() {
        mJniBuffer.reset(mEnv->NewByteArray(kBufferSize));
        return mJniBuffer.get();
    }

    bool IsActive() override {
        return mActive;
    }

    int64_t OnGetSize(uint64_t inode) override {
        return checkException(mEnv->CallLongMethod(mSelf, gOnGetSizeMethod, inode));
    }

    int32_t OnOpen(uint64_t inode) override {
        return checkException(mEnv->CallIntMethod(mSelf, gOnOpenMethod, inode));
    }

    int32_t OnFsync(uint64_t inode) override {
        return checkException(mEnv->CallIntMethod(mSelf, gOnFsyncMethod, inode));
    }

    int32_t OnRelease(uint64_t inode) override {
        if (checkException(mEnv->CallIntMethod(mSelf, gOnReleaseMethod, inode)) == -1) {
            mActive = false;
        }
        return fuse::kFuseSuccess;
    }

    int32_t OnRead(uint64_t inode, uint64_t offset, uint32_t size, void* buffer) override {
        CHECK_LE(size, static_cast<uint32_t>(kBufferSize));
        const int32_t result = checkException(mEnv->CallIntMethod(
                mSelf, gOnReadMethod, inode, offset, size, mJniBuffer.get()));
        if (result <= 0) {
            return result;
        }
        if (result > static_cast<int32_t>(size)) {
            LOG(ERROR) << "Returned size is too large.";
            return -EIO;
        }

        mEnv->GetByteArrayRegion(mJniBuffer.get(), 0, result, static_cast<jbyte*>(buffer));
        CHECK(!mEnv->ExceptionCheck());

        return checkException(result);
    }

    int32_t OnWrite(uint64_t inode, uint64_t offset, uint32_t size, const void* buffer) override {
        CHECK_LE(size, static_cast<uint32_t>(kBufferSize));

        mEnv->SetByteArrayRegion(mJniBuffer.get(), 0, size, static_cast<const jbyte*>(buffer));
        CHECK(!mEnv->ExceptionCheck());

        return checkException(mEnv->CallIntMethod(
                mSelf, gOnWriteMethod, inode, offset, size, mJniBuffer.get()));
    }
};

jboolean com_android_internal_os_FuseAppLoop_start_loop(JNIEnv* env, jobject self, jint jfd) {
    base::unique_fd fd(jfd);
    Callback callback(env, self);

    if (!callback.Init()) {
        LOG(ERROR) << "Failed to init callback";
        return JNI_FALSE;
    }

    return fuse::StartFuseAppLoop(fd.release(), &callback);
}

const JNINativeMethod methods[] = {
    {
        "native_start_loop",
        "(I)Z",
        (void *) com_android_internal_os_FuseAppLoop_start_loop
    }
};

}  // namespace

int register_com_android_internal_os_FuseAppLoop(JNIEnv* env) {
    gFuseAppLoopClass = MakeGlobalRefOrDie(env, FindClassOrDie(env, CLASS_NAME));
    gOnGetSizeMethod = GetMethodIDOrDie(env, gFuseAppLoopClass, "onGetSize", "(J)J");
    gOnOpenMethod = GetMethodIDOrDie(env, gFuseAppLoopClass, "onOpen", "(J)I");
    gOnFsyncMethod = GetMethodIDOrDie(env, gFuseAppLoopClass, "onFsync", "(J)I");
    gOnReleaseMethod = GetMethodIDOrDie(env, gFuseAppLoopClass, "onRelease", "(J)I");
    gOnReadMethod = GetMethodIDOrDie(env, gFuseAppLoopClass, "onRead", "(JJI[B)I");
    gOnWriteMethod = GetMethodIDOrDie(env, gFuseAppLoopClass, "onWrite", "(JJI[B)I");
    RegisterMethodsOrDie(env, CLASS_NAME, methods, NELEM(methods));
    return 0;
}

}  // namespace android