/*
 * Copyright (c) 2025 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 <cerrno>
#include <cstring>
#include <dlfcn.h>
#include <set>
#include <string>
#include <unistd.h>
#include <utility>
#include <vector>
#include <sys/mman.h>
#include <sys/wait.h>
#include <sys/prctl.h>
#include <fcntl.h>

#include "appspawn_hook.h"
#include "appspawn_server.h"
#include "appspawn_service.h"
#include "appspawn_manager.h"
#include "appspawn_utils.h"
#ifdef ARKWEB_UTILS_ENABLE
#include "arkweb_utils.h"
#endif
#include "command_lexer.h"
#include "config_policy_utils.h"
#include "hitrace_meter.h"
#include "js_runtime.h"
#include "json_utils.h"
#include "parameters.h"
#include "resource_manager.h"
#ifndef APPSPAWN_TEST
#include "ace_forward_compatibility.h"
#include "main_thread.h"
#include "runtime.h"
#endif
#include "parameter.h"

using namespace OHOS::AppSpawn;
using namespace OHOS::Global;

#define SYSTEMLIB_JSON "/appspawn_systemLib.json"
#ifdef ASAN_DETECTOR
static const bool DEFAULT_PRELOAD_VALUE = false;
#else
static const bool DEFAULT_PRELOAD_VALUE = true;
#endif
static const bool DEFAULT_PRELOAD_ETS_VALUE = true;
static const std::string PRELOAD_JSON_CONFIG("/appspawn_preload.json");
static const std::string PRELOAD_ETS_JSON_CONFIG("/appspawn_preload_ets.json");
static const std::string PRELINK_DSO_LIST_CONFIG("etc/appspawn/appspawn_prelink_dso_list");
static const bool DEFAULT_SPAWN_UNIFIED_VALUE = false;

typedef struct TagParseJsonContext {
    std::set<std::string> names;
    std::string key;
} ParseJsonContext;

static void GetNames(const cJSON *root, std::set<std::string> &names, std::string key)
{
    cJSON *namesJson = cJSON_GetObjectItemCaseSensitive(root, key.c_str());
    APPSPAWN_CHECK_ONLY_EXPER(namesJson != nullptr, return);

    uint32_t count = (uint32_t)cJSON_GetArraySize(namesJson);
    for (uint32_t i = 0; i < count; ++i) {
        const char *name = cJSON_GetStringValue(cJSON_GetArrayItem(namesJson, i));
        APPSPAWN_CHECK_ONLY_EXPER(name != nullptr, continue);
        APPSPAWN_LOGV("name %{public}s", name);
        if (!names.count(name)) {
            names.insert(name);
        }
    }
}

static int GetNameSet(const cJSON *root, ParseJsonContext *context)
{
    GetNames(root, context->names, context->key);
    return 0;
}

static void PreloadModule(bool isHybrid)
{
    bool preloadEts = OHOS::system::GetBoolParameter("persist.appspawn.preloadets", DEFAULT_PRELOAD_ETS_VALUE);
    bool unified = OHOS::system::GetBoolParameter("persist.appspawn.hybridspawn.unified", DEFAULT_SPAWN_UNIFIED_VALUE);
    APPSPAWN_LOGI("LoadExtendLib: preloadets is %{public}s, unified is %{public}s, isHybrid is %{public}s",
        preloadEts ? "true" : "false", unified ? "true" : "false", isHybrid ? "true" : "false");

    OHOS::AbilityRuntime::Runtime::Options options;
    options.loadAce = true;
    options.preload = true;
    options.lang = OHOS::AbilityRuntime::Runtime::Language::JS;
    if ((isHybrid || unified) && preloadEts) {
        options.lang = OHOS::AbilityRuntime::Runtime::Language::ETS;
    }
    auto runtime = OHOS::AbilityRuntime::Runtime::Create(options);
    if (!runtime) {
        APPSPAWN_LOGE("LoadExtendLib: Failed to create runtime");
        return;
    }

    ParseJsonContext jsContext = {{}, "napi"};
    (void)ParseJsonConfig("etc/appspawn", PRELOAD_JSON_CONFIG.c_str(), GetNameSet, &jsContext);
    for (std::string moduleName : jsContext.names) {
        APPSPAWN_LOGI("moduleName %{public}s", moduleName.c_str());
        runtime->PreloadSystemModule(moduleName);
    }

    ParseJsonContext etsContext = {{}, "class"};
    (void)ParseJsonConfig("etc/appspawn", PRELOAD_ETS_JSON_CONFIG.c_str(), GetNameSet, &etsContext);
    for (std::string className : etsContext.names) {
        APPSPAWN_LOGI("className %{public}s", className.c_str());
        runtime->PreloadSystemClass(className.c_str());
    }

    OHOS::AbilityRuntime::Runtime::SavePreloaded(std::move(runtime));
}

static const int PARAM_BUFFER_SIZE = 256;

static bool IsPrelinkEnable()
{
    char buffer[PARAM_BUFFER_SIZE] = {0};
    int ret = GetParameter("const.startup.prelink.enable", "true", buffer, PARAM_BUFFER_SIZE);
    if (ret <= 0) {
        APPSPAWN_LOGW("get prelink enable param unsuccess! ret =%{public}d", ret);
        return true;
    }

    if (strcmp(buffer, "true") == 0) {
        return true;
    }

    return false;
}

APPSPAWN_STATIC int PrelinkerMain(int prelinkMemfd)
{
    if (fcntl(prelinkMemfd, F_SETFD, 0) < 0) {
        APPSPAWN_LOGE("prelinker fcntl failed, err=%{public}s", strerror(errno));
        return -1;
    }

    const char *listFile = nullptr;
    CfgFiles *files = GetCfgFiles(PRELINK_DSO_LIST_CONFIG.c_str());
    if (files == nullptr) {
        APPSPAWN_LOGE("prelink list not found");
        return -1;
    }
    for (int i = MAX_CFG_POLICY_DIRS_CNT - 1; i >= 0; --i) {
        if (files->paths[i] != nullptr) {
            listFile = files->paths[i];
            break;
        }
    }
    if (listFile == nullptr) {
        APPSPAWN_LOGE("prelink list file not found");
        FreeCfgFiles(files);
        return -1;
    }
    APPSPAWN_LOGI("Enable Prelink from %{public}s", PRELINK_DSO_LIST_CONFIG.c_str());

    int rc = dlprelink_record(prelinkMemfd, listFile);
    if (rc != 0) {
        APPSPAWN_LOGE("prelinker record failed, err=%{public}s", dlerror());
    }

    FreeCfgFiles(files);
    return rc;
}

static void PrelinkLibs()
{
    int ws = -1;

    if (!IsPrelinkEnable()) {
        APPSPAWN_LOGI("prelink disabled");
        return;
    }

    /* Prelinker process and children apps will automatically share reserved area from fork() */
    if (dlprelink_reserve_mem() < 0) {
        APPSPAWN_LOGE("dlprelink_reserve_mem failed");
        return;
    }

    int prelinkMemfd = memfd_create("relro_cache", MFD_ALLOW_SEALING);
    if (prelinkMemfd < 0) {
        APPSPAWN_LOGE("memfd_create failed, err=%{public}s", strerror(errno));
        return;
    }

    pid_t prelinkerPid = fork();
    if (prelinkerPid < 0) {
        APPSPAWN_LOGE("prelinker fork failed, err=%{public}s", strerror(errno));
        close(prelinkMemfd);
        return;
    } else if (prelinkerPid == 0) {
        int rc = PrelinkerMain(prelinkMemfd);
        _exit(rc);
    }

    if (waitpid(prelinkerPid, &ws, 0) < 0) {
        APPSPAWN_LOGE("prelinker waitpid failed, err=%{public}s", strerror(errno));
        close(prelinkMemfd);
        return;
    }
    prelinkerPid = 0;
    if (!WIFEXITED(ws) || WEXITSTATUS(ws) != 0) {
        APPSPAWN_LOGE("prelinker execution failed, ws = %{public}d", ws);
        close(prelinkMemfd);
        return;
    }

    if (fcntl(prelinkMemfd, F_ADD_SEALS, F_SEAL_SEAL | F_SEAL_GROW | F_SEAL_SHRINK | F_SEAL_FUTURE_WRITE) < 0) {
        APPSPAWN_LOGE("prelinkMemfd fcntl failed, err=%{public}s", strerror(errno));
        close(prelinkMemfd);
        return;
    }

    if (dlprelink_register(prelinkMemfd) < 0) {
        APPSPAWN_LOGE("dlprelink_register failed");
        close(prelinkMemfd);
        return;
    }
    APPSPAWN_LOGI("PrelinkLibs SUCCESS!");
}

APPSPAWN_STATIC int PreLinkAppSpawn(AppSpawnMgr *content)
{
    if (!content || content->content.mode != MODE_FOR_APP_SPAWN) {
        return 0;
    }

    PrelinkLibs();
    return 0;
}

static void LoadExtendLib(AppSpawnMgr *content)
{
    const char *acelibdir = OHOS::Ace::AceForwardCompatibility::GetAceLibName();
    APPSPAWN_LOGI("LoadExtendLib: Start calling dlopen acelibdir");
    void *aceAbilityLib = dlopen(acelibdir, RTLD_NOW | RTLD_LOCAL);
    APPSPAWN_CHECK(aceAbilityLib != nullptr, return, "Fail to dlopen %{public}s, [%{public}s]", acelibdir, dlerror());
    APPSPAWN_LOGI("LoadExtendLib: Success to dlopen %{public}s", acelibdir);

    OHOS::AppExecFwk::MainThread::PreloadExtensionPlugin();
    bool preload = OHOS::system::GetBoolParameter("persist.appspawn.preload", DEFAULT_PRELOAD_VALUE);
    bool isColdRun = IsColdRunMode(content) ? true : false;
    APPSPAWN_LOGI("LoadExtendLib: preload is %{public}s, isColdRun is %{public}s, mode is %{public}d",
        preload ? "true" : "false", isColdRun ? "true" : "false", content->content.mode);
    if ((!preload) || isColdRun) {
        APPSPAWN_LOGI("LoadExtendLib: Do not preload VM");
        return;
    }

    APPSPAWN_LOGI("LoadExtendLib: Start preload VM");
    SetTraceDisabled(true);
    PreloadModule(IsHybridSpawnMode(content));
    SetTraceDisabled(false);

    Resource::ResourceManager *systemResMgr = Resource::GetSystemResourceManagerNoSandBox();
    APPSPAWN_CHECK(systemResMgr != nullptr, return, "Fail to get system resource manager");
    APPSPAWN_LOGI("LoadExtendLib: End preload VM");
}

static void PreloadCJLibs(void)
{
    const char* cjEnvLibName = "libcj_environment.z.so";
    const char* cjEnvInitName = "OHOS_InitSpawnEnv";
    void* cjEnvLib = dlopen(cjEnvLibName, RTLD_NOW | RTLD_LOCAL);
    APPSPAWN_CHECK(cjEnvLib != nullptr, return, "Failed to dlopen %{public}s, [%{public}s]", cjEnvLibName, dlerror());
    auto symbol = dlsym(cjEnvLib, cjEnvInitName);
    if (!symbol) {
        dlclose(cjEnvLib);
        APPSPAWN_LOGE("Failed to dlsym %{public}s, [%{public}s]", cjEnvInitName, dlerror());
        return;
    }
    auto initSpawnEnv = reinterpret_cast<void (*)()>(symbol);
    initSpawnEnv();
}

static void LoadExtendCJLib(void)
{
    const char *acelibdir = OHOS::Ace::AceForwardCompatibility::GetAceLibName();
    APPSPAWN_LOGI("LoadExtendLib: Start calling dlopen acelibdir.");
    void *aceAbilityLib = dlopen(acelibdir, RTLD_NOW | RTLD_LOCAL);
    APPSPAWN_CHECK(aceAbilityLib != nullptr, return, "Fail to dlopen %{public}s, [%{public}s]", acelibdir, dlerror());
    APPSPAWN_LOGI("LoadExtendLib: Success to dlopen %{public}s", acelibdir);

    OHOS::AppExecFwk::MainThread::PreloadExtensionPlugin();

    PreloadCJLibs();
}

static int BuildFdInfoMap(const AppSpawnMsgNode *message, std::map<std::string, int> &fdMap, int isColdRun)
{
    APPSPAWN_CHECK_ONLY_EXPER(message != nullptr && message->buffer != nullptr, return -1);
    APPSPAWN_CHECK_ONLY_EXPER(message->tlvOffset != nullptr, return -1);
    int findFdIndex = 0;
    AppSpawnMsgReceiverCtx recvCtx;
    if (!isColdRun) {
        APPSPAWN_CHECK_ONLY_EXPER(message->connection != nullptr, return -1);
        recvCtx = message->connection->receiverCtx;
        if (recvCtx.fdCount <= 0) {
            APPSPAWN_LOGI("no need to build fd info %{public}d, %{public}d", recvCtx.fds != NULL, recvCtx.fdCount);
            return 0;
        }
    }
    for (uint32_t index = TLV_MAX; index < (TLV_MAX + message->tlvCount); index++) {
        if (message->tlvOffset[index] == INVALID_OFFSET) {
            return -1;
        }
        uint8_t *data = message->buffer + message->tlvOffset[index];
        if (((AppSpawnTlv *)data)->tlvType != TLV_MAX) {
            continue;
        }
        AppSpawnTlvExt *tlv = (AppSpawnTlvExt *)data;
        if (strcmp(tlv->tlvName, MSG_EXT_NAME_APP_FD) != 0) {
            continue;
        }
        std::string key((char *)data + sizeof(AppSpawnTlvExt));
        if (isColdRun) {
            std::string envKey = std::string(APP_FDENV_PREFIX) + key;
            char *fdChar = getenv(envKey.c_str());
            APPSPAWN_CHECK(fdChar != nullptr, continue, "getfd from env failed %{public}s", envKey.c_str());
            int fd = atoi(fdChar);
            APPSPAWN_CHECK(fd > 0, continue, "getfd from env atoi errno %{public}s,%{public}d", envKey.c_str(), fd);
            fdMap[key] = fd;
        } else {
            APPSPAWN_CHECK(findFdIndex < recvCtx.fdCount && recvCtx.fds[findFdIndex] > 0,
                return -1, "invalid fd info  %{public}d %{public}d", findFdIndex, recvCtx.fds[findFdIndex]);
            fdMap[key] = recvCtx.fds[findFdIndex++];
            if (findFdIndex >= recvCtx.fdCount) {
                break;
            }
        }
    }
    return 0;
}

APPSPAWN_STATIC void ClearEnvAndReturnSuccess(AppSpawnContent *content, AppSpawnClient *client)
{
    AppSpawningCtx *property = (AppSpawningCtx *)client;
    APPSPAWN_CHECK(content != nullptr && property != nullptr, return, "invalid param in clearEnv");
    int fd = property->forkCtx.fd[1];
    property->forkCtx.fd[1] = -1;
    AppSpawnEnvClear(content, client);
    APPSPAWN_CHECK(fd >= 0, return, "invalid fd for notify parent");
    int ret = 0;
    ssize_t len = write(fd, &ret, sizeof(ret));
    (void)close(fd);
    APPSPAWN_CHECK_ONLY_DUMPW(len == sizeof(ret), "error %{public}d len %{public}zd", errno, len);
}

APPSPAWN_STATIC int RunChildThread(const AppSpawnMgr *content, const AppSpawningCtx *property)
{
    std::string checkExit;
    if (OHOS::system::GetBoolParameter("persist.init.debug.checkexit", true)) {
        checkExit = std::to_string(getpid());
    }
#ifdef ARKWEB_UTILS_ENABLE
    const char* bundleName = GetBundleName(property);
    std::string bundleNameStr = (bundleName != nullptr) ? std::string(bundleName) : "";
    OHOS::ArkWeb::SelectWebcoreBeforeProcessRun(bundleNameStr);
#endif
    setenv(APPSPAWN_CHECK_EXIT, checkExit.c_str(), true);
    if (CheckAppMsgFlagsSet(property, APP_FLAGS_CHILDPROCESS)) {
        std::map<std::string, int> fdMap;
        BuildFdInfoMap(property->message, fdMap, IsColdRunMode(content));
        ClearEnvAndReturnSuccess((AppSpawnContent *)&content->content, (AppSpawnClient *)&property->client);
        OHOS::AppExecFwk::MainThread::StartChild(fdMap);
    } else {
        ClearEnvAndReturnSuccess((AppSpawnContent *)&content->content, (AppSpawnClient *)&property->client);
        OHOS::AppExecFwk::MainThread::Start();
    }
    unsetenv(APPSPAWN_CHECK_EXIT);
    return 0;
}

APPSPAWN_STATIC int RunChildByRenderCmd(const AppSpawnMgr *content, const AppSpawningCtx *property)
{
    uint32_t len = 0;
    char *renderCmd = reinterpret_cast<char *>(GetAppPropertyExt(property, MSG_EXT_NAME_RENDER_CMD, &len));
    if (renderCmd == nullptr || !IsDeveloperModeOn(property)) {
        APPSPAWN_LOGE("Denied launching a native process: not in developer mode");
        return -1;
    }
    APPSPAWN_LOGI("renderCmd %{public}s", renderCmd);
    std::vector<std::string> args;
    std::string command(renderCmd);
    CommandLexer lexer(command);
    if (!lexer.GetAllArguments(args)) {
        return -1;
    }
    if (args.empty()) {
        APPSPAWN_LOGE("Failed to run a native process: empty command %{public}s", renderCmd);
        return -1;
    }
    std::vector<char *> options;
    for (const auto &arg : args) {
        options.push_back(const_cast<char *>(arg.c_str()));
    }
    options.push_back(nullptr);
    // clear appspawn env, do not user any content and property
    ClearEnvAndReturnSuccess((AppSpawnContent *)&content->content, (AppSpawnClient *)&property->client);
    execvp(args[0].c_str(), options.data());
    // If it succeeds calling execvp, it never returns.
    int err = errno;
    APPSPAWN_LOGE("Failed to launch a native process with execvp: %{public}s", strerror(err));
    return 0;
}

static int RunChildProcessor(AppSpawnContent *content, AppSpawnClient *client)
{
    APPSPAWN_CHECK(client != nullptr && content != nullptr, return -1, "Invalid client");
    AppSpawningCtx *property = reinterpret_cast<AppSpawningCtx *>(client);
    int ret = 0;
    if (GetAppSpawnMsgType(property) == MSG_SPAWN_NATIVE_PROCESS) {
        ret = RunChildByRenderCmd(reinterpret_cast<AppSpawnMgr *>(content), property);
    } else {
        ret = RunChildThread(reinterpret_cast<AppSpawnMgr *>(content), property);
    }
    return ret;
}

APPSPAWN_STATIC int PreLoadAppSpawn(AppSpawnMgr *content)
{
    if (IsNWebSpawnMode(content)) {
        return 0;
    }
#ifdef ARKWEB_UTILS_ENABLE
    if (IsAppSpawnMode(content)) {
        OHOS::ArkWeb::CreateArkWebSandboxPathIfNeed();
    }
#endif
    // register
    RegChildLooper(&content->content, RunChildProcessor);
    if (strcmp(content->content.longProcName, CJAPPSPAWN_SERVER_NAME) == 0) {
        LoadExtendCJLib();
        return 0;
    }

    LoadExtendLib(content);
    return 0;
}

APPSPAWN_STATIC int DoDlopenLibs(const cJSON *root, ParseJsonContext *context)
{
    cJSON *systemLibs = cJSON_GetObjectItemCaseSensitive(root, "systemLib");
    if (systemLibs == nullptr) {
        return 0;
    }

    uint32_t libsCount = (uint32_t)cJSON_GetArraySize(systemLibs);
    for (uint32_t i = 0; i < libsCount; ++i) {
        const char *libName = cJSON_GetStringValue(cJSON_GetArrayItem(systemLibs, i));
        char reaLibPath[PATH_MAX] = {0};
        if (libName == nullptr || realpath(libName, reaLibPath) == nullptr) {
            continue;
        }
        APPSPAWN_LOGV("reaLibPath %{public}s", reaLibPath);
        void *lib = dlopen(reaLibPath, RTLD_LAZY);
        if (lib == nullptr) {
            APPSPAWN_LOGE("FAILED to dlopen %{public}s %{public}s", libName, dlerror());
        }
    }
    return 0;
}

APPSPAWN_STATIC int DlopenAppSpawn(AppSpawnMgr *content)
{
    if (!(IsAppSpawnMode(content) || IsHybridSpawnMode(content))) {
        return 0;
    }
    (void)ParseJsonConfig("etc/appspawn", SYSTEMLIB_JSON, DoDlopenLibs, nullptr);

#ifdef ARKWEB_UTILS_ENABLE
    OHOS::ArkWeb::PreloadArkWebLibForBrowser();
#endif
    APPSPAWN_LOGI("DlopenAppSpawn: Start reclaim file cache");
    OHOS::Ace::AceForwardCompatibility::ReclaimFileCache(getpid());
    return 0;
}

APPSPAWN_STATIC int ProcessSpawnDlopenMsg(AppSpawnMgr *content)
{
#ifdef ARKWEB_UTILS_ENABLE
    // dlopen libarkweb_engine.so
    OHOS::ArkWeb::DlopenArkWebLib();
    OHOS::Ace::AceForwardCompatibility::ReclaimFileCache(getpid());
#endif
    return 0;
}

APPSPAWN_STATIC int ProcessSpawnDlcloseMsg(AppSpawnMgr *content)
{
#ifdef ARKWEB_UTILS_ENABLE
    // dlclose libarkweb_engine.so
    return OHOS::ArkWeb::DlcloseArkWebLib();
#endif
    return 0;
}

MODULE_CONSTRUCTOR(void)
{
    APPSPAWN_LOGV("Load ace module ...");
    AddPreloadHook(HOOK_PRIO_HIGHEST, PreLoadAppSpawn);
    AddPreloadHook(HOOK_PRIO_HIGHEST, DlopenAppSpawn);
    AddPreloadHook(HOOK_PRIO_HIGHEST, PreLinkAppSpawn);
    AddServerStageHook(STAGE_SERVER_ARKWEB_PRELOAD, HOOK_PRIO_COMMON, ProcessSpawnDlopenMsg);
    AddServerStageHook(STAGE_SERVER_ARKWEB_UNLOAD, HOOK_PRIO_COMMON, ProcessSpawnDlcloseMsg);
}