/*
 * 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 specic language governing permissions and
 * limitations under the License.
 */

// Need to use LOGE_EX.
#define LOG_TAG "AppFuseBridge"

#include <android_runtime/Log.h>
#include <android-base/logging.h>
#include <android-base/unique_fd.h>
#include <core_jni_helpers.h>
#include <libappfuse/FuseBridgeLoop.h>
#include <libappfuse/FuseBuffer.h>
#include <nativehelper/JNIHelp.h>

namespace android {
namespace {

constexpr const char* CLASS_NAME = "com/android/server/storage/AppFuseBridge";
static jclass gAppFuseClass;
static jmethodID gAppFuseOnMount;
static jmethodID gAppFuseOnClosed;

class Callback : public fuse::FuseBridgeLoopCallback {
    JNIEnv* mEnv;
    jobject mSelf;

public:
    Callback(JNIEnv* env, jobject self) : mEnv(env), mSelf(self) {}
    void OnMount(int mount_id) override {
        mEnv->CallVoidMethod(mSelf, gAppFuseOnMount, mount_id);
        if (mEnv->ExceptionCheck()) {
            LOGE_EX(mEnv, nullptr);
            mEnv->ExceptionClear();
        }
    }

    void OnClosed(int mount_id) override {
        mEnv->CallVoidMethod(mSelf, gAppFuseOnClosed, mount_id);
        if (mEnv->ExceptionCheck()) {
            LOGE_EX(mEnv, nullptr);
            mEnv->ExceptionClear();
        }
    }
};

class MonitorScope final {
public:
    MonitorScope(JNIEnv* env, jobject obj) : mEnv(env), mObj(obj), mLocked(false) {
        if (mEnv->MonitorEnter(obj) == JNI_OK) {
            mLocked = true;
        } else {
            LOG(ERROR) << "Failed to enter monitor.";
        }
    }

    ~MonitorScope() {
        if (mLocked) {
            if (mEnv->MonitorExit(mObj) != JNI_OK) {
                LOG(ERROR) << "Failed to exit monitor.";
            }
        }
    }

    operator bool() {
        return mLocked;
    }

private:
    // Lifetime of |MonitorScope| must be shorter than the reference of mObj.
    JNIEnv* mEnv;
    jobject mObj;
    bool mLocked;

    DISALLOW_COPY_AND_ASSIGN(MonitorScope);
};

jlong com_android_server_storage_AppFuseBridge_new(JNIEnv* env, jobject self) {
    return reinterpret_cast<jlong>(new fuse::FuseBridgeLoop());
}

void com_android_server_storage_AppFuseBridge_delete(JNIEnv* env, jobject self, jlong java_loop) {
    fuse::FuseBridgeLoop* const loop = reinterpret_cast<fuse::FuseBridgeLoop*>(java_loop);
    CHECK(loop);
    delete loop;
}

void com_android_server_storage_AppFuseBridge_start_loop(
        JNIEnv* env, jobject self, jlong java_loop) {
    fuse::FuseBridgeLoop* const loop = reinterpret_cast<fuse::FuseBridgeLoop*>(java_loop);
    CHECK(loop);
    Callback callback(env, self);
    loop->Start(&callback);
}

jint com_android_server_storage_AppFuseBridge_add_bridge(
        JNIEnv* env, jobject self, jlong java_loop, jint mountId, jint javaDevFd) {
    base::unique_fd devFd(javaDevFd);
    fuse::FuseBridgeLoop* const loop = reinterpret_cast<fuse::FuseBridgeLoop*>(java_loop);
    CHECK(loop);

    base::unique_fd proxyFd[2];
    if (!fuse::SetupMessageSockets(&proxyFd)) {
        return -1;
    }

    if (!loop->AddBridge(mountId, std::move(devFd), std::move(proxyFd[0]))) {
        return -1;
    }

    return proxyFd[1].release();
}

const JNINativeMethod methods[] = {
    {
        "native_new",
        "()J",
        reinterpret_cast<void*>(com_android_server_storage_AppFuseBridge_new)
    },
    {
        "native_delete",
        "(J)V",
        reinterpret_cast<void*>(com_android_server_storage_AppFuseBridge_delete)
    },
    {
        "native_start_loop",
        "(J)V",
        reinterpret_cast<void*>(com_android_server_storage_AppFuseBridge_start_loop)
    },
    {
        "native_add_bridge",
        "(JII)I",
        reinterpret_cast<void*>(com_android_server_storage_AppFuseBridge_add_bridge)
    }
};

}  // namespace

void register_android_server_storage_AppFuse(JNIEnv* env) {
    CHECK(env != nullptr);

    gAppFuseClass = MakeGlobalRefOrDie(env, FindClassOrDie(env, CLASS_NAME));
    gAppFuseOnMount = GetMethodIDOrDie(env, gAppFuseClass, "onMount", "(I)V");
    gAppFuseOnClosed = GetMethodIDOrDie(env, gAppFuseClass, "onClosed", "(I)V");
    RegisterMethodsOrDie(env, CLASS_NAME, methods, NELEM(methods));
}
}  // namespace android
