/* app/builder/KMBuild.cpp
 *
 * Copyright (C) 2023 OpenKylin, Inc
 */

#include "KMBuild.h"

#include <gio/gio.h>
#include <glib.h>
#include <sys/ioctl.h>
#include <sys/personality.h>
#include <array>
#include <filesystem>
#include <limits>
#include <string>
#include <fcntl.h>
#include <grp.h>
#include <pwd.h>
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/utsname.h>
#include <unistd.h>
#include <optional>

#include "common/KMBuildinsUtils.h"
#include "common/KMCommonTypePrivate.h"
#include "common/KMException.h"
#include "common/KMExecerWrap.h"
#include "common/KMInfoJson.h"
#include "common/KMInstalledAppQuery.h"
#include "common/KMLogger.h"
#include "common/KMOptions.h"
#include "common/KMUtil.h"
#include "common/kmtranslation.h"

#include "KMContainerWrap.h"

namespace fs = std::filesystem;

class KMBuild::Options : public KMOptions
{
public:
    Options() = default;
    virtual ~Options() = default;

protected:
    void preParseHook() override;
    void postParseHook() override;

private:
    void addOptions();

public:
    // application options
    bool m_help = false;
    bool m_dieWithParent = false;
    bool m_withAppdir = false;
    std::vector<std::string> m_bindMounts;
    std::string m_buildDir;

    // context options
    std::vector<std::string> m_envs;
    std::string m_envFD;
    // TODO: 下面权限相关的暂时不用
    std::vector<std::string> m_shares;
    std::vector<std::string> m_unshares;
    std::vector<std::string> m_sockets;
    std::vector<std::string> m_nosockets;
    std::vector<std::string> m_devices;
    std::vector<std::string> m_nodevices;
    std::vector<std::string> m_allows;
    std::vector<std::string> m_disallows;
    std::vector<std::string> m_filesystems;
    std::vector<std::string> m_nofilesystems;
    std::vector<std::string> m_unsetenvs;
    std::vector<std::string> m_ownNames;
    std::vector<std::string> m_talkNames;
    std::vector<std::string> m_notalkNames;
    std::vector<std::string> m_systemOwnNames;
    std::vector<std::string> m_systemTalkNames;
    std::vector<std::string> m_systemNoTalkNames;
    std::vector<std::string> m_addPolicys;
    std::vector<std::string> m_removePolicys;
    std::vector<std::string> m_persists;

    // position options
    std::string m_directory;
    std::string m_command;
    std::vector<std::string> m_args;
};

void KMBuild::Options::preParseHook()
{
    // kaiming build [OPTION…] DIRECTORY [COMMAND [ARGUMENT…]]
    int posDirectory = getPositionalOptionIndex();
    if (m_argc > posDirectory + 2)
    {
        int posArgs = posDirectory + 2;

        char **args = m_argv + posArgs;
        int count = m_argc - posArgs;
        for (int i = 0; i < count; i++)
        {
            m_args.push_back(*(args + i));
        }

        m_argc = posArgs;
    }

    addOptions();
}

void KMBuild::Options::postParseHook()
{
    if (m_help)
    {
        showUsage();
        exit(EXIT_SUCCESS);
    }

    if (m_directory.empty())
    {
        KMError(_("DIRECTORY must be specified"));
        showUsage();
        exit(EXIT_FAILURE);
    }

    if (m_command.empty())
    {
        m_command = "/bin/sh";
    }
}

void KMBuild::Options::addOptions()
{
    m_optionsDescription = std::make_unique<opt::options_description>(_("\nUsage:\n \tkaiming build [OPTION…] DIRECTORY [COMMAND [ARGUMENT…]] - Build in directory "
                                                                        "\nExample:\n \tkaiming build ${build-dir} cmake -j8"
                                                                        "\n\nOptions"));

    opt::options_description helpOptions(_("Help Options"));
    helpOptions.add_options()("help,h", opt::bool_switch(&m_help), _("Show help options"));
    opt::options_description appOptions(_("Application Options"));
    appOptions.add_options()("bind-mount", opt::value<std::vector<std::string>>(&m_bindMounts), _("Add bind mount, --bind-mount=DEST=SRC"))
                            ("build-dir", opt::value<std::string>(&m_buildDir), _("Start build in this directory, --build-dir=DIR"))
                            ("with-appdir", opt::bool_switch(&m_withAppdir), _("Export application homedir directory to build"))
                            // ("die-with-parent,p", opt::bool_switch(&m_dieWithParent), _("Kill processes when the parent process dies"))
                            ;

    // TODO：权限设置方式未确定，暂时不使用权限相关选项：contextOptions
    opt::options_description contextOptions(_("Context Options"));
    contextOptions.add_options()
                                // ("share", opt::value<std::vector<std::string>>(&m_shares), _("Share with host, --share=SHARE"))
                                // ("unshare", opt::value<std::vector<std::string>>(&m_unshares), _("Unshare with host, --unshare=SHARE"))
                                // ("socket", opt::value<std::vector<std::string>>(&m_sockets), _("Expose socket to app, --socket=SOCKET"))
                                // ("nosocket", opt::value<std::vector<std::string>>(&m_nosockets), _("Don't expose socket to app, --nosocket=SOCKET"))
                                // ("device", opt::value<std::vector<std::string>>(&m_devices), _("Expose device to app, --device=DEVICE"))
                                // ("nodevice", opt::value<std::vector<std::string>>(&m_nodevices), _("Don't expose device to app, --nodevice=DEVICE"))
                                // ("allow", opt::value<std::vector<std::string>>(&m_allows), _("Allow feature, --allow=FEATURE"))
                                // ("disallow", opt::value<std::vector<std::string>>(&m_disallows), _("Don't allow feature, -disallow=FEATURE"))
                                // ("filesystem", opt::value<std::vector<std::string>>(&m_filesystems), _("Expose filesystem to app (:ro for read-only), --filesystem=FILESYSTEM[:ro]"))
                                // ("nofilesystem", opt::value<std::vector<std::string>>(&m_nofilesystems), _("Don't expose filesystem to app, --nofilesystem=FILESYSTEM"))
                                ("env", opt::value<std::vector<std::string>>(&m_envs), _("Set environment variable, --env=VAR=VALUE"))
                                ("env-fd", opt::value<std::string>(&m_envFD), _("Read environment variables in env -0 format from FD, --env-fd=FD"))
                                ("unset-env", opt::value<std::vector<std::string>>(&m_unsetenvs), _("Remove variable from environment, --unset-env=VAR"))
                                // ("own-name", opt::value<std::vector<std::string>>(&m_ownNames), _("Allow app to own name on the session bus, --own-name=DBUS_NAME"))
                                // ("talk-name", opt::value<std::vector<std::string>>(&m_talkNames), _("Allow app to talk to name on the session bus, --talk-name=DBUS_NAME"))
                                // ("no-talk-name", opt::value<std::vector<std::string>>(&m_notalkNames), _("Don't allow app to talk to name on the session bus, --no-talk-name=DBUS_NAME"))
                                // ("system-own-name", opt::value<std::vector<std::string>>(&m_systemOwnNames), _("Allow app to own name on the system bus, --system-own-name=DBUS_NAME"))
                                // ("system-talk-name", opt::value<std::vector<std::string>>(&m_systemTalkNames), _("Allow app to talk to name on the system bus, --system-talk-name=DBUS_NAME"))
                                // ("system-no-talk-name", opt::value<std::vector<std::string>>(&m_systemNoTalkNames), _("Don't allow app to talk to name on the system bus, --system-no-talk-name=DBUS_NAME"))
                                // ("add-policy", opt::value<std::vector<std::string>>(&m_addPolicys), _("Add generic policy option, --add-policy=SUBSYSTEM.KEY=VALUE"))
                                // ("remove-policy", opt::value<std::vector<std::string>>(&m_removePolicys), _("Remove generic policy option, --remove-policy=SUBSYSTEM.KEY=VALUE"))
                                // ("persist", opt::value<std::vector<std::string>>(&m_persists), _("Persist home directory subpath, --persist=FILENAME"))
                                ;

    opt::options_description positionalOptions(_("Positional Options"));
    positionalOptions.add_options()("DIRECTORY", opt::value<std::string>(&m_directory), _("The app dir"));
    positionalOptions.add_options()("COMMAND", opt::value<std::string>(&m_command), _("COMMAND"));

    m_optionsDescription->add(helpOptions).add(appOptions).add(contextOptions).add(positionalOptions);

    m_optionsPositionalDescription->add("DIRECTORY", 1);
    m_optionsPositionalDescription->add("COMMAND", 1);
}

class KMBuild::Private
{
public:
    std::unique_ptr<Options> m_kmOptions;

    std::string m_command;
    std::string m_metadata;
    std::shared_ptr<KMInfoJson> m_infoJson;
    KMRef m_baseRef;
    KMRef m_runtimeRef;
    std::string m_id;

    std::shared_ptr<KMDeploy> m_baseDeploy;
    std::shared_ptr<KMDeploy> m_runtimeDeploy;
    std::string m_baseFiles;
    std::string m_runtimeFiles;
    std::string m_varTmpDir;
    std::string m_varLibDir;
    std::string m_resFiles;
    std::string m_appIdDir;
    std::string m_workdir;
    std::string m_mergedir;

    std::unique_ptr<KMContainerWrap> m_container;
    std::shared_ptr<KMContext> m_appContext;
    std::shared_ptr<KMContext> m_argContext;

    uid_t m_uid = 0;
    gid_t m_gid = 0;

    // TODO : 后面成员废弃
    std::shared_ptr<KMMetaData> m_kmmetadata;
    bool m_isApp = false;
    bool m_isExtension = false;
    bool m_isAppExtension = false;
    bool m_isCustomUsr = false;

    std::string m_extensionofRef;
    std::string m_extensionofTag;
    std::shared_ptr<KMDeploy> m_extensionofDeploy;
    std::string m_runtimeKey;
    std::string m_runtimePref;
    KMRef m_runtimeKMRef;
    std::string m_usrDir;
    std::shared_ptr<KMMetaData> m_runtimeMetaData;
    bool m_appFiles_ro = false;
    std::string m_extensionTmpfsPoint;
    std::string m_bareExtensionPoint;
    std::string m_extensionPoint;

    std::unique_ptr<KMExecerWrap> m_execer;
    KMRunFlags m_runFlags = KMRunFlags::KAIMING_RUN_FLAG_DEFAULT;
};

REGISTER_SUBCOMMAND_DYNCREATE(build, KMBuild)

KMBuild::KMBuild()
    : d(std::make_unique<Private>())
{
    d->m_kmOptions = std::make_unique<Options>();

    enableVfs();
}

KMBuild::~KMBuild() { }

int KMBuild::despose(int argc, char **argv)
{
    KMTrace("KMBuild::despose invoke begin");

    init(argc, argv);

    int ret = run();

    KMTrace("KMBuild::despose invoke end");
    return ret;
}


void KMBuild::init(int argc, char **argv)
{
    KMTrace("KMBuild::init invoke begin");

    d->m_kmOptions->parseCommandLine(argc, argv);
    parseOptionsToContext();

    d->m_command = d->m_kmOptions->m_command;
    d->m_metadata = KMUtil::buildFilename(d->m_kmOptions->m_directory, "info.json");
    d->m_infoJson = std::make_shared<KMInfoJson>();
    d->m_infoJson->loadFile(d->m_metadata);
    d->m_id = d->m_infoJson->id;

    // 解析base、runtime，并校验是否已经安装
    KMRef originalBaseRef = d->m_infoJson->baseRef();
    // 校验base是否已安装
    KMInstalledAppQuery query;
    std::vector<KMRef> bases = query.channel(originalBaseRef.channel)
                                    .arch(originalBaseRef.arch)
                                    .kind(BASE_TYPE_BASE)
                                    .id(originalBaseRef.id)
                                    .module(MODULE_NAME_DEV)
                                    .version(originalBaseRef.version)
                                    .query();
    if (bases.empty())
    {
        throw KMException("No matching development base found, please install it : kaiming install " 
                        + KMUtil::buildFilename(originalBaseRef.id, originalBaseRef.version, MODULE_NAME_DEV, originalBaseRef.channel));
    }
    d->m_baseRef = bases.at(0);
    d->m_baseDeploy = KMStorageDir::getSystemDefaultDir().loadDeployed(d->m_baseRef);
    if (!d->m_baseDeploy)
    {
        throw KMException(_("The base's info.json load failed"));
    }
    d->m_baseFiles = KMUtil::buildFilename(d->m_baseDeploy->m_dir, "files");

    // 解析runtime，并校验是否已经安装
    if (!d->m_infoJson->runtime.empty())
    {
        KMRef originalRuntimeRef = d->m_infoJson->runtimeRef();
        std::vector<KMRef> runtimes = query.channel(originalRuntimeRef.channel)
                                           .arch(originalRuntimeRef.arch)
                                           .kind(BASE_TYPE_RUNTIME)
                                           .id(originalRuntimeRef.id)
                                           .module(MODULE_NAME_DEV)
                                           .version(originalRuntimeRef.version)
                                           .query();
        if (runtimes.empty())
        {
            throw KMException("No matching development runtime found, please install it : kaiming install " 
                            + KMUtil::buildFilename(originalRuntimeRef.id, originalRuntimeRef.version, MODULE_NAME_DEV, originalRuntimeRef.channel));
        }
        d->m_runtimeRef = runtimes.at(0);

        d->m_runtimeDeploy = KMStorageDir::getSystemDefaultDir().loadDeployed(d->m_runtimeRef);
        if (!d->m_runtimeDeploy)
        {
            throw KMException(_("The runtime's info.json load failed"));
        }

        d->m_runtimeFiles = KMUtil::buildFilename(d->m_runtimeDeploy->m_dir, "files");
    }

    d->m_varTmpDir = KMUtil::buildFilename(d->m_kmOptions->m_directory, "var/tmp");
    std::error_code ec;
    if (!KMUtil::mkpath(d->m_varTmpDir, ec))
    {
        throw KMException(d->m_varTmpDir + _(" create failed, error: ") + ec.message());
    }

    d->m_workdir = KMUtil::buildFilename(d->m_varTmpDir, "workdir");
    if (!KMUtil::mkpath(d->m_workdir, ec))
    {
        throw KMException(d->m_workdir + _(" create failed, error: ") + ec.message());
    }

    d->m_mergedir = KMUtil::buildFilename(d->m_varTmpDir, "mergedir");
    if (!KMUtil::mkpath(d->m_mergedir, ec))
    {
        throw KMException(d->m_mergedir + _(" create failed, error: ") + ec.message());
    }

    d->m_varLibDir = KMUtil::buildFilename(d->m_kmOptions->m_directory, "var/lib");
    if (!KMUtil::mkpath(d->m_varLibDir, ec))
    {
        throw KMException(d->m_varLibDir + _(" create failed, error: ") + ec.message());
    }

    d->m_resFiles = KMUtil::buildFilename(d->m_kmOptions->m_directory, "files");

    d->m_appIdDir = KMStorageDir::getDataDir(d->m_id);

    // TODO : 预留。1、从base和runtime继承权限？2、主机文件系统读写权限？3、从命令行传入的权限？
    d->m_appContext = std::make_shared<KMContext>();
    d->m_appContext->merge(*d->m_argContext);

    KMTrace("KMBuild::init invoke end");
}

void KMBuild::computePermissions(const std::shared_ptr<KMMetaData> &appMetadata, const std::shared_ptr<KMMetaData> &runtimeMetadata)
{
    KMTrace("KMBuild::computePermissions invoke begin");

    std::shared_ptr<KMContext> context = std::make_shared<KMContext>();
    bool load = false;

    if (runtimeMetadata)
    {
        if (!context->loadMetadata(runtimeMetadata.get()))
        {
            throw KMException(_("Get runtime permissions failed"));
        }

        /* Don't inherit any permissions from the runtime, only things like env vars. */
        context->resetPermissions();
        load = true;
    }

    if (appMetadata)
    {
        if (!context->loadMetadata(appMetadata.get()))
        {
            throw KMException(_("Get app permissions failed"));
        }
        load = true;
    }

    if (!load)
    {
        throw KMException(_("Compute app permissions failed"));
    }

    d->m_appContext = context;

    KMTrace("KMBuild::computePermissions invoke end");
}

void KMBuild::parseOptionsToContext()
{
    KMTrace("KMBuild::parseOptionsToContext invoke begin");

    d->m_argContext = std::make_shared<KMContext>();

    // TODO：权限设置方式未确定，暂时不使用权限相关选项
    // setOptionSharesContext();
    // setOptionSocketsContext();
    // setOptionDevicesContext();
    // setOptionFeaturesContext();
    // setOptionFilesystemContext();
    setOptionEnvContext();
    // setOptionOwnNameContext();
    // setOptionTalkNameContext();
    // setOptionSystemDbusContext();
    // setOptionGenericPolicyContext();
    // setOptionPersistContext();

    KMTrace("KMBuild::parseOptionsToContext invoke end");
}

void KMBuild::setOptionSharesContext()
{
    for (auto &share : d->m_kmOptions->m_shares)
    {
        if (!d->m_argContext->addShares(share))
        {
            throw KMException(_("Wrong option --share=") + share);
        }
    }

    for (auto &unshare : d->m_kmOptions->m_unshares)
    {
        if (!d->m_argContext->removeShares(unshare))
        {
            throw KMException(_("Wrong option --unshare=") + unshare);
        }
    }
}

void KMBuild::setOptionSocketsContext()
{
    for (auto &socket : d->m_kmOptions->m_sockets)
    {
        if (!d->m_argContext->addSockets(socket))
        {
            throw KMException(_("Wrong option --socket=") + socket);
        }
    }

    for (auto &nosocket : d->m_kmOptions->m_nosockets)
    {
        if (!d->m_argContext->removeSockets(nosocket))
        {
            throw KMException(_("Wrong option --nosocket=") + nosocket);
        }
    }
}

void KMBuild::setOptionDevicesContext()
{
    for (auto &device : d->m_kmOptions->m_devices)
    {
        if (!d->m_argContext->addDevices(device))
        {
            throw KMException(_("Wrong  option --device=") + device);
        }
    }

    for (auto &nodevice : d->m_kmOptions->m_nodevices)
    {
        if (!d->m_argContext->removeDevices(nodevice))
        {
            throw KMException(_("Wrong option --nodevice=") + nodevice);
        }
    }
}

void KMBuild::setOptionFeaturesContext()
{
    for (auto &allow : d->m_kmOptions->m_allows)
    {
        if (!d->m_argContext->addFeatures(allow))
        {
            throw KMException(_("Wrong option --allow=") + allow);
        }
    }

    for (auto &disallow : d->m_kmOptions->m_disallows)
    {
        if (!d->m_argContext->removeFeatures(disallow))
        {
            throw KMException(_("Wrong option --disallow=") + disallow);
        }
    }
}

void KMBuild::setOptionFilesystemContext()
{
    std::string filesystem_out;
    KMFilesystemMode mode = KMFilesystemMode::KAIMING_FILESYSTEM_MODE_NONE;

    for (auto &filesystem : d->m_kmOptions->m_filesystems)
    {
        if (!KMContext::parseFilesystem(filesystem, false, filesystem_out, mode))
        {
            throw KMException(_("Wrong option --filesystem=") + filesystem);
        }

        d->m_argContext->takeFilesystem(filesystem_out, mode);
    }

    for (auto &nofilesystem : d->m_kmOptions->m_nofilesystems)
    {
        if (!KMContext::parseFilesystem(nofilesystem, true, filesystem_out, mode))
        {
            throw KMException(_("Wrong option --nofilesystem=") + nofilesystem);
        }

        d->m_argContext->takeFilesystem(filesystem_out, KAIMING_FILESYSTEM_MODE_NONE);
    }
}

void KMBuild::parseEnvFd()
{
    if (d->m_kmOptions->m_envFD.empty())
    {
        return;
    }

    unsigned long long fd = std::stoull(d->m_kmOptions->m_envFD);
    if (fd > INT_MAX)
    {
        throw KMException(_("Not a valid file descriptor: ") + d->m_kmOptions->m_envFD);
    }

    ssize_t filesize = lseek(fd, 0L, SEEK_END);
    lseek(fd, 0L, SEEK_SET);

    char *buffer = new char[filesize + 1];
    std::shared_ptr<char> data(buffer, [fd](auto p) {
        delete[] p;

        if (fd >= 3)
            close(fd);
    });
    ssize_t bytesRead = read(fd, buffer, filesize);
    if (bytesRead < 0)
    {
        throw KMException(_("Can not read file descriptor: ") + d->m_kmOptions->m_envFD);
    }

    parseEnvBlock(buffer, bytesRead);
}

void KMBuild::parseEnvBlock(const char *data, std::size_t length)
{
    if (data == nullptr)
    {
        return;
    }

    const char *p = data;
    std::size_t remaining = length;

    /* data might not be \0-terminated */
    while (remaining > 0)
    {
        std::size_t len = strnlen(p, remaining);
        assert(len <= remaining);

        std::string item(p);
        std::string::size_type pos = item.find("=");
        if (std::string::npos == pos)
        {
            throw KMException(_("Environment variable must be given in the form VARIABLE=VALUE, not ") + item);
        }

        std::string var = item.substr(0, pos);
        std::string value = item.substr(pos + 1);
        d->m_argContext->addEnvVar(var, value);

        p += len;
        remaining -= len;

        if (remaining > 0)
        {
            assert(*p == '\0');
            p += 1;
            remaining -= 1;
        }
    }
}

void KMBuild::setOptionEnvContext()
{
    parseEnvFd();

    for (const std::string &env : d->m_kmOptions->m_envs)
    {
        std::string::size_type pos = env.find("=");
        if (std::string::npos == pos)
        {
            throw KMException(_("Wrong option --env=") + env);
        }

        std::string var = env.substr(0, pos);
        std::string value = env.substr(pos + 1);
        d->m_argContext->addEnvVar(var, value);
    }

    for (const auto &unenv : d->m_kmOptions->m_unsetenvs)
    {
        if (KMUtil::contains(unenv, "="))
        {
            throw KMException(_("Wrong option --unset-env=") + unenv + _(". Environment variable name must not contain '='"));
        }

        d->m_argContext->addEnvVar(unenv, "");
    }
}

bool KMBuild::verifyDbusName(const std::string &dbusName)
{
    std::string namePart(dbusName);
    if (KMUtil::endsWith(dbusName, ".*"))
    {
        namePart = dbusName.substr(0, dbusName.length() - 2);
    }

    if (g_dbus_is_name(namePart.c_str()) && !g_dbus_is_unique_name(namePart.c_str()))
    {
        return true;
    }

    return false;
}

void KMBuild::setOptionOwnNameContext()
{
    for (auto &ownName : d->m_kmOptions->m_ownNames)
    {
        if (!verifyDbusName(ownName))
        {
            throw KMException(_("Invalid dbus name ") + ownName);
        }

        d->m_argContext->setSessionBusPolicy(ownName, KAIMING_POLICY_OWN);
    }
}

void KMBuild::setOptionTalkNameContext()
{
    for (auto &talkName : d->m_kmOptions->m_talkNames)
    {
        if (!verifyDbusName(talkName))
        {
            throw KMException(_("Invalid dbus name ") + talkName);
        }

        d->m_argContext->setSessionBusPolicy(talkName, KAIMING_POLICY_TALK);
    }

    for (auto &notalkName : d->m_kmOptions->m_notalkNames)
    {
        if (!verifyDbusName(notalkName))
        {
            throw KMException(_("Invalid dbus name ") + notalkName);
        }

        d->m_argContext->setSessionBusPolicy(notalkName, KAIMING_POLICY_NONE);
    }
}

void KMBuild::setOptionSystemDbusContext()
{
    for (auto &ownName : d->m_kmOptions->m_systemOwnNames)
    {
        if (!verifyDbusName(ownName))
        {
            throw KMException(_("Invalid dbus name ") + ownName);
        }

        d->m_argContext->setSystemBusPolicy(ownName, KAIMING_POLICY_OWN);
    }

    for (auto &talkName : d->m_kmOptions->m_systemTalkNames)
    {
        if (!verifyDbusName(talkName))
        {
            throw KMException(_("Invalid dbus name ") + talkName);
        }

        d->m_argContext->setSystemBusPolicy(talkName, KAIMING_POLICY_TALK);
    }

    for (auto &notalkName : d->m_kmOptions->m_systemNoTalkNames)
    {
        if (!verifyDbusName(notalkName))
        {
            throw KMException(_("Invalid dbus name ") + notalkName);
        }

        d->m_argContext->setSystemBusPolicy(notalkName, KAIMING_POLICY_NONE);
    }
}

void KMBuild::verifyPolicy(const std::string &policy, std::string &key, std::string &value)
{
    std::vector<std::string> policyPair = KMUtil::splitString(policy, "=");
    if (policyPair.size() != 2)
    {
        throw KMException(_("--add-policy or --remove-policy arguments must be in the form SUBSYSTEM.KEY=VALUE"));
    }

    key = policyPair.at(0);
    std::vector<std::string> keyPair = KMUtil::splitString(key, ".");
    if (keyPair.size() != 2)
    {
        throw KMException(_("--add-policy or --remove-policy arguments must be in the form SUBSYSTEM.KEY=VALUE"));
    }

    value = policyPair.at(1);
    if (KMUtil::startsWith(value, "!"))
    {
        throw KMException(_("--add-policy or --remove-policy values can't start with \"!\""));
    }
}

void KMBuild::setOptionGenericPolicyContext()
{
    for (auto &policy : d->m_kmOptions->m_addPolicys)
    {
        std::string key, value;
        verifyPolicy(policy, key, value);
        d->m_argContext->applyGenericPolicy(key, value);
    }

    for (auto &rmpolicy : d->m_kmOptions->m_removePolicys)
    {
        std::string key, value;
        verifyPolicy(rmpolicy, key, value);
        value = std::string("!") + value;
        d->m_argContext->applyGenericPolicy(key, value);
    }
}

void KMBuild::setOptionPersistContext()
{
    for (auto &persist : d->m_kmOptions->m_persists)
    {
        d->m_argContext->addPersistent(persist, 1);
    }
}

void KMBuild::mountOverlays()
{
    d->m_uid = ::getuid();
    struct passwd *pwd = ::getpwuid(d->m_uid);
    if (pwd == nullptr)
    {
        throw KMException("Couldn't getpwuid");
    }
    d->m_gid = pwd->pw_gid;

    // 提权
    ::setuid(0);
    ::setgid(0);

    std::string lowerdir("lowerdir=");
    if (!d->m_runtimeFiles.empty())
    {
        lowerdir.append(d->m_runtimeFiles);
        lowerdir.append(":");
    }
    lowerdir.append(d->m_baseFiles);

    std::string upperdir("upperdir=");
    upperdir.append(d->m_resFiles);

    std::string workdir("workdir=");
    workdir.append(d->m_workdir);

    std::string mergedir = d->m_mergedir;

    std::vector<std::string> args;
    args.push_back("-t");
    args.push_back("overlay");
    args.push_back("overlay");
    args.push_back("-o");
    args.push_back(lowerdir + "," + upperdir + "," + workdir);
    args.push_back(mergedir);

    std::string result;
    if (!KMUtil::spawn("mount", args, result))
    {
        throw KMException(result);
    }
    else if (!result.empty())
    {
        KMDebug(result);
    }

    // 权限还原
    ::setuid(d->m_uid);
    ::setgid(d->m_gid);
}

void KMBuild::setProcess()
{
    Process p = d->m_container->getProcess();
    p.cwd = d->m_kmOptions->m_buildDir.empty() ? "/app" : d->m_kmOptions->m_buildDir;

    uid_t uid = ::getuid();
    struct passwd *pwd = ::getpwuid(uid);
    if (pwd == nullptr)
    {
        throw KMException("Couldn't getpwuid");
    }
    User user;
    user.uid = uid;
    user.gid = pwd->pw_gid;
    user.username = pwd->pw_name;
    p.user = user;

    std::vector<std::string> args;
    args.push_back(d->m_command);
    args.insert(args.end(), d->m_kmOptions->m_args.begin(), d->m_kmOptions->m_args.end());
    p.args = args;

    d->m_container->setProcess(p);
    d->m_container->setupProcessEnvs();
}

void KMBuild::setupOverlayExtra()
{
    std::vector<std::string> varLayer = {
        "/var/lib/dpkg",
        "/var/cache/apt"
    };
    for (auto& var : varLayer)
    {
        std::string upperdir = KMUtil::buildFilename(d->m_kmOptions->m_directory, var);
        if (fs::exists(var) && KMUtil::mkpath(upperdir))
        {
            d->m_container->lowerToUpperForSubDir(var, upperdir);
            d->m_container->addOverlayfs(var, upperdir, d->m_workdir, var);
        }
    }

    // std::vector<std::string> hostLayer = {
    //     "/usr/share/themes",
    // };
}

void KMBuild::setupAppInfo()
{
    d->m_container->setupAppDataDir(d->m_id);

    d->m_container->addMount(d->m_resFiles, "/app", "bind", { "rbind" });
    if (!d->m_kmOptions->m_buildDir.empty() && fs::exists(d->m_kmOptions->m_buildDir))
    {
        d->m_container->addMount(d->m_kmOptions->m_buildDir, d->m_kmOptions->m_buildDir, "bind", { "rbind" });
    }

    // 我们在下面添加了实际的绑定，这样我们就不会被其他扩展或它们的tmpf所遮挡
    d->m_container->addEnv("KAIMING_DEST", "/app");
    d->m_container->addEnv("KAIMING_ID", d->m_id);
    d->m_container->addEnv("KAIMING_ARCH", d->m_baseDeploy->m_ref.arch);

    /* 在/var中持久化一些东西。我们不能持久化所有东西，因为这会破坏主机工作的/var东西。例如原子上的/home->/var/home。
       在构建过程中要包含的有趣的东西是/var/tmp（用于构建过程中共享的tempfiles）和/var/lib/rpm之类的东西，如果安装使用包的话。
     */
    // overlay后不在如下面这样挂载
    d->m_container->addMount(d->m_varLibDir, "/var/lib", "bind", { "rbind" });
    d->m_container->addMount(d->m_varTmpDir, "/var/tmp", "bind", { "rbind" });
    if (fs::exists("/etc/apt"))
    {
        // 在容器中运行apt设置，未解决问题：dpkg: 错误: 所请求的操作需要超级用户权限
        d->m_container->addMount("/etc/apt", "/etc/apt", "bind", { "rbind" });
        d->m_container->addShareMount("/var/lib/dpkg/lock-frontend", "", "/var/lib/dpkg/lock-frontend", { "rbind" });
    }

    d->m_container->setupAppContext(d->m_appContext);

    for (const std::string &bindMount : d->m_kmOptions->m_bindMounts)
    {
        size_t pos = bindMount.find_first_of('=');
        if (std::string::npos == pos)
        {
            throw KMException(_("Missing '=' in bind mount option : ") + bindMount);
        }

        d->m_container->addMount(bindMount.substr(pos + 1), bindMount.substr(0, pos), "bind", { "rbind" });
    }

    setProcess();
}

int KMBuild::run()
{
    KMTrace("KMBuild::run invoke begin");

    // 此处无法成功提权，放弃此种方式，使用KMContainerWrap::setupOverlayfs替代
    // mountOverlays();

    d->m_container = std::make_unique<KMContainerWrap>();
    d->m_container->setAppID(d->m_id);
    d->m_container->initMinimalEnvs(true, false);
    d->m_container->setupOverlayfs(d->m_baseFiles, d->m_runtimeFiles, d->m_resFiles, d->m_workdir);
    d->m_container->setupBase(d->m_baseDeploy);
    d->m_container->setupRuntime(d->m_runtimeDeploy);
    setupAppInfo();
    
    if (!d->m_container->run())
    {
        throw KMException(_("Failed to run ") + d->m_command);
    }

    KMTrace("KMBuild::run invoke end");
    return EXIT_SUCCESS;
}

void KMBuild::setRunFlags()
{
    KMRunFlags runFlags = (KMRunFlags)(KMRunFlags::KAIMING_RUN_FLAG_DEVEL | 
                                       KMRunFlags::KAIMING_RUN_FLAG_MULTIARCH | 
                                       KMRunFlags::KAIMING_RUN_FLAG_NO_SESSION_HELPER | 
                                       KMRunFlags::KAIMING_RUN_FLAG_SET_PERSONALITY | 
                                       KMRunFlags::KAIMING_RUN_FLAG_NO_A11Y_BUS_PROXY);
    
    if (d->m_kmOptions->m_dieWithParent)
    {
        runFlags = (KMRunFlags)(runFlags | KMRunFlags::KAIMING_RUN_FLAG_DIE_WITH_PARENT);
    }

    if (d->m_isCustomUsr)
    {
        runFlags = (KMRunFlags)(runFlags | KMRunFlags::KAIMING_RUN_FLAG_WRITABLE_ETC);
    }

    runFlags = (KMRunFlags)(runFlags | d->m_argContext->getRunFlags());

    /* Unless manually specified, we disable dbus proxy */
    if (!d->m_appContext->getNeedsSessionBusProxy())
    {
        runFlags = (KMRunFlags)(runFlags | KMRunFlags::KAIMING_RUN_FLAG_NO_SESSION_BUS_PROXY);
    }

    if (!d->m_appContext->getNeedsSystemBusProxy())
    {
        runFlags = (KMRunFlags)(runFlags | KMRunFlags::KAIMING_RUN_FLAG_NO_SYSTEM_BUS_PROXY);
    }

    /* Never set up an a11y bus for builds */
    runFlags = (KMRunFlags)(runFlags | KMRunFlags::KAIMING_RUN_FLAG_NO_A11Y_BUS_PROXY);

    d->m_runFlags = runFlags;
}

