/*
 * Copyright (c) 2020-2021 Huawei Device Co., Ltd.
 * 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.
 */

#include <dlfcn.h>
#include <gtest/gtest.h>
#include "log.h"
#include "utils.h"
#include "libfs.h"
#include "KernelConstants.h"

using namespace testing::ext;

class DlopenTest : public testing::Test {
};

#define RES_DIR_DYLOAD RES_DIR_KERNEL "dyload/"
#define DYLOAD_TEST_DIR "/storage/data/"

/**
 * @tc.number   SUB_KERNEL_DL_SO_0100
 * @tc.name     the elf does not depend on any user-so, dlopen a full path so
 * @tc.desc     [C- SOFTWARE -0200]
 */
HWTEST_F(DlopenTest, testDlopenFullPathSo, Function | MediumTest | Level1)
{
    char* resSO = RES_DIR_DYLOAD "libdso1.so";
    char* newSO = DYLOAD_TEST_DIR "libdso1.so";

    // test SetUp
    ASSERT_EQ(CopyFile(resSO, newSO), 0);
    LOG("SetUp ok");

    // test in child process
    pid_t pid = fork();
    ASSERT_TRUE(pid >= 0) << "======== Fork Error! =========";
    if (pid == 0) { // child
        LOG("newSO: %s", newSO);
        void* h = dlopen(newSO, RTLD_NOW);
        if (!h) {
            PANIC("dlopen %s failed: %s\n", newSO, dlerror());
        }
        LOG("dlopen '%s' ok", newSO);
        int* i = (int*) dlsym(h, "g_int");
        if (!i) {
            PANIC("dlsym g_int failed: %s", dlerror());
        }
        if (*i != 1) {
            PANIC("so initialization failed: want i=1 got i=%d", *i);
        }

        int (*f)(void) = (int (*)(void))dlsym(h, "inc_i");
        if (!f) {
            PANIC("dlsym func 'inc_i' failed: %s", dlerror());
        }
        f();
        if (*i != 2) {
            PANIC("inc_i call failed: want i=2 got i=%d", *i);
        }
        exit(0);
    } else { // parent
        Msleep(100);
        WaitProcExitedOK(pid);
    }

    // test TearDown
    ASSERT_EQ(RemoveFile(newSO), 0);
    LOG("TearDown ok ");
}

/**
 * @tc.number   SUB_KERNEL_DL_SO_0200
 * @tc.name     the elf doesn't depend on any user-so, dlopen a relative-path so, twice
 * @tc.desc     [C- SOFTWARE -0200]
 */
HWTEST_F(DlopenTest, testDlopenRelativePathSo, Function | MediumTest | Level1)
{
    char* resSO = RES_DIR_DYLOAD "libdso1.so";
    char* testDir = DYLOAD_TEST_DIR "target";
    char* testSo = DYLOAD_TEST_DIR "target/libdso1.so";
    // test SetUp
    char* curPath = GetCurrentPath();
    ASSERT_NE(MakeDir(testDir), -1);
    ASSERT_EQ(CopyFile(resSO, testSo), 0);
    EXPECT_EQ(chdir(DYLOAD_TEST_DIR), 0);
    LOG("SetUp ok");

    // test in child process, so that this load-so will not effect other test
    pid_t pid = fork();
    ASSERT_TRUE(pid >= 0) << "======== Fork Error! =========";
    if (pid == 0) { // child
        void* h = dlopen("./target/libdso1.so", RTLD_NOW);
        if (!h) {
            PANIC("dlopen './target/libdso1.so' failed: %s", dlerror());
        }
        int* i = (int*) dlsym(h, "g_int");
        if (!i) {
            PANIC("dlsym g_int failed: %s", dlerror());
        }
        if (*i != 1) {
            PANIC("so initialization failed: want i=1 got i=%d", *i);
        }
        if (dlclose(h)) {
            PANIC("dlclose failed: %s", dlerror());
        }

        void* g = dlopen("../data/target/libdso1.so", RTLD_NOW);
        if (!g) {
            PANIC("dlopen so again failed: %s",  dlerror());
        }
        if (h != g) {
            PANIC("dlopen same so return diff handle");
        }
        int (*f)(void) = (int (*)(void))dlsym(h, "inc_i");
        if (!f) {
            PANIC("dlsym func 'inc_i' failed: %s", dlerror());
        }
        f();
        if (*i != 2) {
            PANIC("inc_i call failed: want i=2 got i=%d", *i);
        }
        if (dlclose(h)) {
            PANIC("dlclose failed: %s", dlerror());
        }
        LOG("Pass\n");
        exit(0);
    } else { // parent
        Msleep(100);
        WaitProcExitedOK(pid);
    }

    // test TearDown
    EXPECT_EQ(chdir(curPath), 0);
    ASSERT_EQ(RemoveFile(testSo), 0);
    LOG("TearDown ok ");
}

/**
 * @tc.number   SUB_KERNEL_DL_SO_0400
 * @tc.name     the elf doesn't depend on any user-so. dlopen a not exist so.
 * @tc.desc     [C- SOFTWARE -0200]
 */
HWTEST_F(DlopenTest, testDlopenNotExistSo, Function | MediumTest | Level3)
{
    dlerror(); // clear any old error message
    char* errMsg = dlerror();
    if (errMsg) {
        LOG("dlerror should return NULL when called twice.");
        FAIL();
    }

    void* h = dlopen("not_exist_dso1.so", RTLD_NOW);
    if (h) {
        LOG("dlopen a non-exist-so should return NULL!");
        FAIL();
    }
    errMsg = dlerror();
    if (!errMsg) {
        LOG("dlerror return NULL!");
        FAIL();
    }
    char* p = strcasestr(errMsg, "No such file");
    if (!p) {
        LOG("dlerror msg invalid, should include 'No such file', actual msg=%s", errMsg);
        FAIL();
    }
}

/**
 * @tc.number   SUB_KERNEL_DL_SO_0500
 * @tc.name     the elf doesn't depend on any user-so, and dlopen continuously loads a same so
 * @tc.desc     [C- SOFTWARE -0200]
 */
HWTEST_F(DlopenTest, testDlopenSameSo, Function | MediumTest | Level2)
{
    char* resSO = RES_DIR_DYLOAD "libdso1.so";
    char* newSO = DYLOAD_TEST_DIR "libdso1.so";

    // test SetUp
    ASSERT_EQ(CopyFile(resSO, newSO), 0);
    LOG("SetUp ok");

    // test in child process
    pid_t pid = fork();
    ASSERT_TRUE(pid >= 0) << "======== Fork Error! =========";
    if (pid == 0) { // child
        void* h1 = dlopen(newSO, RTLD_NOW);
        if (!h1) {
            PANIC("dlopen %s failed: %s\n", newSO, dlerror());
        }
        void* h2 = dlopen(newSO, RTLD_NOW);
        if (!h2) {
            PANIC("dlopen %s failed: %s\n", newSO, dlerror());
        }
        if (h1 != h2) {
            PANIC("dlopen same so return diff handle");
        }
        if (dlclose(h1)) {
            PANIC("dlclose failed: %s", dlerror());
        }
        if (dlclose(h2)) {
            PANIC("dlclose second handle failed: %s", dlerror());
        }
        exit(0);
    } else { // parent
        Msleep(100);
        WaitProcExitedOK(pid);
    }

    // test TearDown
    ASSERT_EQ(RemoveFile(newSO), 0);
    LOG("TearDown ok ");
}

/**
 * @tc.number   SUB_KERNEL_DL_SO_0800
 * @tc.name     The tested elf depends on full-rpath so, and test same-symbol behaviour.
 *              and three so's have a same symbol
 * @tc.desc     [C- SOFTWARE -0200]
 */
HWTEST_F(DlopenTest, testDlopenSameSymbolSo, Function | MediumTest | Level2)
{
    char* testELF = RES_DIR_DYLOAD "dyload_rpath_full";
    char* resSO1  = RES_DIR_DYLOAD "libdso1.so";
    char* resSO2  = RES_DIR_DYLOAD "libdso2.so";
    char* resSO3  = RES_DIR_DYLOAD "libdso3.so";
    char* newSO1 = DYLOAD_TEST_DIR "libdso1.so";
    char* newSO2 = DYLOAD_TEST_DIR "libdso2.so";
    char* newSO3 = DYLOAD_TEST_DIR "libdso3.so";

    // test SetUp
    EXPECT_EQ(CopyFile(resSO1, newSO1), 0);
    EXPECT_EQ(CopyFile(resSO2, newSO2), 0);
    EXPECT_EQ(CopyFile(resSO3, newSO3), 0);
    LOG("SetUp ok");

    // test
    int rt = RunElf(testELF, NULL, NULL);
    EXPECT_EQ(rt, 0) << "same-symbol test failed! exitcode=" << rt;

    // test TearDown
    EXPECT_EQ(RemoveFile(newSO1), 0);
    EXPECT_EQ(RemoveFile(newSO2), 0);
    EXPECT_EQ(RemoveFile(newSO3), 0);
    LOG("TearDown ok ");
}

/**
 * @tc.number   SUB_KERNEL_DL_SO_1000
 * @tc.name     the test elf compiled with rpath, and the depended-so is in that path
 * @tc.desc     [C- SOFTWARE -0200]
 */
HWTEST_F(DlopenTest, testDlopenSoInRelativeRpath, Function | MediumTest | Level2)
{
    char* testELF = RES_DIR_DYLOAD "dyload_rpath_relative";
    char* resSO = RES_DIR_DYLOAD "libdso1.so";
    char* newSO = "./lib/libdso1.so";

    // test SetUp
    ASSERT_NE(MakeDir("./lib"), -1);
    ASSERT_EQ(CopyFile(resSO, newSO), 0);
    LOG("SetUp ok");
    Msleep(5000);

    // test
    int rt = RunElf(testELF, NULL, NULL);
    EXPECT_EQ(rt, 0) << "dyload_rpath_relative failed! exitcode=" << rt;

    // test TearDown
    ASSERT_EQ(RemoveFile(newSO), 0);
    LOG("TearDown ok ");
}

