/* common/KMExecerWrap.cpp
 *
 * Copyright (C) 2023 OpenKylin, Inc
 */

#include "KMExecerWrap.h"
#include "config.h"

#include <gio/gio.h>
#include <glib.h>
#include <sys/personality.h>
#include <grp.h>
#include <pwd.h>
#include <sys/types.h>
#include <unistd.h>

#ifdef ENABLE_SECCOMP
#  include <seccomp.h>
#endif

#include <algorithm>
#include <filesystem>
#include <fstream>
#include <set>

#include "KMBuildinsUtils.h"
#include "KMConfig.h"
#include "KMDirFd.h"
#include "KMInstance.h"
#include "KMLogger.h"
#include "KMSeccomp.h"
#include "KMUtil.h"
#include "KMXauth.h"
#include "kmdbusproxy.h"
#include "kmtranslation.h"


#define DEFAULT_SHELL "/bin/sh"

namespace fs = std::filesystem;

class KMExecerWrap::Private
{
public:
    static std::set<std::string> m_usrMergeDirs;
    static std::set<std::string> m_absUsrmergedDirs;

    std::shared_ptr<KMDBusProxy> m_sessionHelper;
    std::shared_ptr<KMInstance> m_instance;
};

std::set<std::string> KMExecerWrap::Private::m_usrMergeDirs = {
    "bin",
    "lib",
    "lib32",
    "lib64",
    "sbin"
};

std::set<std::string> KMExecerWrap::Private::m_absUsrmergedDirs = {
    "/bin",
    "/lib",
    "/lib32",
    "/lib64",
    "/sbin"
};


KMExecerWrap::KMExecerWrap(const std::map<std::string, std::string> &envs)
    : KMExecer(envs)
    , d(std::make_shared<Private>())
{
    setProgress(KMUtil::getKMBox());
    addArg(KMUtil::getKMBox());
}

KMExecerWrap::~KMExecerWrap() { }

bool KMExecerWrap::setupBaseArgv(const std::string &runtimeFiles, const std::string &appidDir, const std::string &arch, KMRunFlags flags)
{
    KMTrace("KMExecerWrap::setupBaseArgv invoke begin");

    uid_t uid = getuid();
    struct passwd *pwd = getpwuid(uid);
    if (pwd == nullptr)
    {
        return false;
    }

    std::string runDir = std::string("/run/user/") + std::to_string(uid);

    std::string passwdContents = std::string(pwd->pw_name) + ":x:"
                                + std::to_string(pwd->pw_uid) + ":"
                                + std::to_string(pwd->pw_gid) + ":"
                                + pwd->pw_name + ":"
                                + pwd->pw_dir + ":"
                                + DEFAULT_SHELL + "\n"
                                + "nfsnobody:x:65534:65534:Unmapped user:/:/sbin/nologin\n";

    struct group *g = getgrgid(pwd->pw_gid);
    std::string groupContents;
    if (g != nullptr)
    {
        groupContents = std::string(g->gr_name) + ":x:" + std::to_string(pwd->pw_gid) + ":" + pwd->pw_name + "\n";
    }
    groupContents += "nfsnobody:x:65534:\n";

    std::string pkcs11ConfContents = "# Disable user pkcs11 config, because the host modules don't work in the runtime\n"
                                     "user-config: none\n";

    if ((flags & KAIMING_RUN_FLAG_NO_PROC) == 0)
    {
        addArgs({ "--proc", "/proc" });
    }

    if ((flags & KAIMING_RUN_FLAG_PARENT_SHARE_PIDS) == 0)
    {
        addArg("--unshare-pid");
    }

    addArgs({ "--dir",
              "/tmp",
              "--dir",
              "/var/tmp",
              "--dir",
              "/run/host",
              "--perms",
              "0700",
              "--dir",
              runDir,
              "--setenv",
              "XDG_RUNTIME_DIR",
              runDir,
              "--symlink",
              "../run",
              "/var/run",
              "--ro-bind",
              "/sys/block",
              "/sys/block",
              "--ro-bind",
              "/sys/bus",
              "/sys/bus",
              "--ro-bind",
              "/sys/class",
              "/sys/class",
              "--ro-bind",
              "/sys/dev",
              "/sys/dev",
              "--ro-bind",
              "/sys/devices",
              "/sys/devices",
              "--ro-bind-try",
              "/proc/self/ns/user",
              "/run/.userns",
              /* glib uses this like /etc/timezone */
              "--symlink",
              "/etc/timezone",
              "/var/db/zoneinfo" });

    if (flags & KAIMING_RUN_FLAG_DIE_WITH_PARENT)
    {
        addArg("--die-with-parent");
    }

    if (flags & KAIMING_RUN_FLAG_WRITABLE_ETC)
    {
        addArgs({ "--dir", "/usr/etc", "--symlink", "usr/etc", "/etc" });
    }

    if (!bindMountData("passwd", passwdContents, "/etc/passwd"))
    {
        return false;
    }

    if (!bindMountData("group", groupContents, "/etc/group"))
    {
        return false;
    }

    if (!bindMountData("pkcs11.conf", pkcs11ConfContents, "/etc/pkcs11/pkcs11.conf"))
    {
        return false;
    }

    if (fs::exists("/etc/machine-id"))
    {
        bindMount("/etc/machine-id", "/etc/machine-id", true);
    }
    else if (fs::exists("/var/lib/dbus/machine-id"))
    {
        bindMount("/var/lib/dbus/machine-id", "/etc/machine-id", true);
    }

    if (!runtimeFiles.empty())
    {
        fs::path runtimePath(runtimeFiles);
        fs::path etc = runtimePath / "etc";

        if ((flags & KAIMING_RUN_FLAG_WRITABLE_ETC) == 0 && fs::exists(etc))
        {
            std::set<string> ignore_paths{ "passwd", "group", "machine-id", "resolv.conf", "host.conf", "hosts", "gai.conf", "localtime", "timezone", "pkcs11" };

            for (auto &subetc : fs::directory_iterator(etc))
            {
                if (ignore_paths.find(subetc.path().filename()) != ignore_paths.end())
                {
                    continue;
                }

                std::string src = etc / subetc.path();
                std::string dest = fs::path("/etc") / subetc.path().filename();

                if (fs::is_symlink(src))
                {
                    string targetPath = fs::read_symlink(src).string();
                    symlink(targetPath, dest);
                }
                else
                {
                    bindMount(src, dest, true);
                }
            }
        }
    }

    if (!appidDir.empty())
    {
        fs::path appidPath(appidDir);
        fs::path appCacheDir = appidPath / "cache";
        fs::path appTmpDir = appCacheDir / "tmp";
        fs::path appDataDir = appidPath / "data";
        fs::path appConfigDir = appidPath / "config";

        /* These are nice to have as a fixed path */
        bindMount(appCacheDir.string(), "/var/cache");
        bindMount(appTmpDir.string(), "/var/tmp");
        bindMount(appDataDir.string(), "/var/data");
        bindMount(appConfigDir.string(), "/var/config");
    }

    setupUsrLinks(runtimeFiles, "");

    setTZDataArgs(runtimeFiles);

    unsigned long int pers = PER_LINUX;
    if ((flags & KAIMING_RUN_FLAG_SET_PERSONALITY) && KMUtil::isLinux32Arch(arch))
    {
        KMDebug("Setting personality linux32");
        pers = PER_LINUX32;
    }

    /* Always set the personallity, and clear all weird flags */
    personality(pers);

#ifdef ENABLE_SECCOMP
    std::string errMsg;
    if (!setupSeccomp(arch, pers, flags, errMsg))
    {
        return false;
    }
#endif

    if ((flags & KAIMING_RUN_FLAG_WRITABLE_ETC) == 0)
    {
        setMonitorPathArgs((flags & KAIMING_RUN_FLAG_NO_SESSION_HELPER) == 0);
    }

    return true;
}

void KMExecerWrap::setMonitorPathArgs(bool useSessionHelper)
{
    std::map<std::string, ::DBus::Variant> ret;
    if (useSessionHelper)
    {
        DBus::Connection connection = DBus::Connection::SessionBus();
        d->m_sessionHelper = std::make_shared<KMDBusProxy>(connection, KAIMING_SESSION_HELPER_PATH, KAIMING_SESSION_HELPER_NAME);
        ret = d->m_sessionHelper->RequestSession();
    }

    if (ret.size() > 0)
    {
        string monitorPath = ret["path"];
        string pkcs11SocketPath = ret["pkcs11-socket"];
        if (monitorPath.length() > 0)
        {
            bindMount(monitorPath, "/run/host/monitor", true);
            symlink("/run/host/monitor/resolv.conf", "/etc/resolv.conf");
            symlink("/run/host/monitor/host.conf", "/etc/host.conf");
            symlink("/run/host/monitor/hosts", "/etc/hosts");
            symlink("/run/host/monitor/gai.conf", "/etc/gai.conf");
        }
        if (pkcs11SocketPath.length() > 0)
        {
            string trustedModuleContents = "# This overrides the runtime p11-kit-trusted module with a client one talking to the trust module on the host\n"
                                             "module: p11-kit-client.so\n";
            if (bindMountData("p11-kit-trust.module", trustedModuleContents, "/etc/pkcs11/modules/p11-kit-trust.module"))
            {
                bindMount(pkcs11SocketPath, "/run/kaiming/p11-kit/pkcs11", true);
                addRuntimeDirMember("p11-kit");
            }
        }
    }
    else
    {
        if (fs::exists("/etc/resolv.conf"))
        {
            bindMount("/etc/resolv.conf", "/etc/resolv.conf", true);
        }

        if (fs::exists("/etc/host.conf"))
        {
            bindMount("/etc/host.conf", "/etc/host.conf", true);
        }

        if (fs::exists("/etc/hosts"))
        {
            bindMount("/etc/hosts", "/etc/hosts", true);
        }

        if (fs::exists("/etc/gai.conf"))
        {
            bindMount("/etc/gai.conf", "/etc/gai.conf", true);
        }
    }

    KMTrace("KMExecerWrap::setupBaseArgv invoke end");
}

void KMExecerWrap::setupUsrLinks(const std::string &runtimeFiles, const std::string &sysroot)
{
    if (runtimeFiles.empty())
    {
        return;
    }

    for (auto &subdir : d->m_usrMergeDirs)
    {
        fs::path runtime_subdir(runtimeFiles + "/" + subdir);

        if (fs::exists(runtime_subdir))
        {
            std::string link = std::string("usr/") + subdir;
            std::string create;

            if (sysroot.empty())
            {
                create = subdir;
            }
            else
            {
                create = sysroot + "/" + subdir;
            }

            symlink(link, create);
        }
        else
        {
            KMDebug(runtime_subdir.string() + " does not exist");
        }
    }
}

/**
 * @brief : 设置系统时间挂载参数，如果系统的存在用系统的，系统的不存在，用 runtime/files 下的
 */
void KMExecerWrap::setTZDataArgs(const std::string &runtimeFiles)
{
    std::string rawTimezone = KMUtil::kmGetTimeZone();
    std::string timezoneContent = rawTimezone + "\n";
    std::string localtimeContent = "../usr/share/zoneinfo/" + rawTimezone;
    std::string runtimeTimezone;

    if (!runtimeFiles.empty())
    {
        runtimeTimezone = runtimeFiles + "/share/zoneinfo";
    }

    if (!runtimeTimezone.empty() && fs::exists(runtimeTimezone))
    {
        /* Check for host /usr/share/zoneinfo */
        if (fs::is_directory("/usr/share/zoneinfo"))
        {
            /* Here we assume the host timezone file exist in the host data */
            bindMount("/usr/share/zoneinfo", "/usr/share/zoneinfo", true);
            symlink(localtimeContent, "/etc/localtime");
        }
        else if (fs::exists(runtimeTimezone + "/" + rawTimezone))
        {
            symlink(localtimeContent, "/etc/localtime");
        }
    }

    bindMountData("timezone", timezoneContent, "/etc/timezone");
}

void KMExecerWrap::applyContextEnvVars(const std::shared_ptr<KMContext> &context)
{
    for (auto &pair : context->getEnvVars())
    {
        if (pair.second.empty())
        {
            removeEnv(pair.first);
        }
        else
        {
            addEnv(pair.first, pair.second);
        }
    }
}

bool KMExecerWrap::addExtensionArgs(const std::shared_ptr<KMMetaData> &metadata, const KMRef &ref, bool useLdSoCache, const std::string &targetPath, std::string &extensionOut, std::string &ldPathOut)
{
    KMTrace("KMExecerWrap::addExtensionArgs invoke begin");

    const std::vector<KMExtension> &extensions = KMBuildinsUtils::listExtensions(metadata, ref.arch, ref.branch);
    if (extensions.empty())
    {
        KMTrace("KMExecerWrap::addExtensionArgs invoke end : no extensions");
        return true;
    }

    /* First we apply all the bindings, they are sorted alphabetically in order for parent directory
       to be mounted before child directories */
    std::vector<KMExtension> pathSortedExtensions(extensions);
    // std::sort(std::execution::seq, pathSortedExtensions.begin(), pathSortedExtensions.end(), [](const KMExtension& a, const KMExtension& b) {
    //     return a.directory.compare(b.directory);
    // });
    std::sort(pathSortedExtensions.begin(), pathSortedExtensions.end());

    std::set<std::string> mountedTmpfs;
    for (auto &extension : pathSortedExtensions)
    {
        std::string directory = KMUtil::buildFilename(targetPath, extension.directory);
        std::string fullDirectory = KMUtil::buildFilename(directory, extension.subdirectorySuffix);
        std::string refFile = KMUtil::buildFilename(fullDirectory, ".ref");
        std::string realRef = KMUtil::buildFilename(extension.extensionFilesPath, directory, ".ref");

        if (extension.needsTmpfs)
        {
            std::string parent = fs::path(directory).parent_path().string();
            if (mountedTmpfs.end() == mountedTmpfs.find(parent))
            {
                addArgs({ "--tmpfs", parent });
                mountedTmpfs.insert(parent);
            }
        }

        addArgs({ "--ro-bind", extension.extensionFilesPath, fullDirectory });

        if (fs::exists(realRef))
        {
            addArgs({ "--lock-file", refFile });
        }
    }

    /* Then apply library directories and file merging, in extension prio order */
    std::string usedExtensions;
    std::string ldLibraryPath;
    int count = 0;
    std::set<std::string> createdSymlink;
    for (const auto &extension : extensions)
    {
        std::string directory = KMUtil::buildFilename(targetPath, extension.directory);
        std::string fullDirectory = KMUtil::buildFilename(directory, extension.subdirectorySuffix);

        if (!usedExtensions.empty())
        {
            usedExtensions.append(";");
        }
        usedExtensions.append(extension.name);
        usedExtensions.append("=");
        if (extension.ref.refCommitId.empty())
        {
            usedExtensions.append("local");
        }
        else
        {
            usedExtensions.append(extension.ref.refCommitId);
        }

        if (!extension.addLdPath.empty())
        {
            std::string ldPath = KMUtil::buildFilename(fullDirectory, extension.addLdPath);

            if (useLdSoCache)
            {
                std::string contents = ldPath;
                contents.append("\n");
                /* We prepend app or runtime and a counter in order to get the include order correct for the conf files */
                std::string ldSoConfFile = ref.kind + "-" + std::to_string(++count) + "-" + extension.name + ".conf";
                std::string ldSoConfFilePath = KMUtil::buildFilename("/run/kaiming/ld.so.conf.d", ldSoConfFile);
                if (bindMountData("ld-so-conf", contents, ldSoConfFilePath))
                {
                    return false;
                }
            }
            else
            {
                if (!ldLibraryPath.empty())
                {
                    ldLibraryPath.append(":");
                }
                ldLibraryPath.append(ldPath);
            }
        }

        for (const auto &mergeDir : extension.mergeDirs)
        {
            std::string parent = fs::path(directory).parent_path().string();
            std::string merge_dir = KMUtil::buildFilename(parent, mergeDir);
            std::string sourceDir = KMUtil::buildFilename(extension.extensionFilesPath, mergeDir);

            if (!fs::exists(sourceDir))
            {
                KMWarn(sourceDir + " does not exists, please check the metadata of " + KMUtil::dupPref(ref) + " : mergeDirs attribute of extension " + extension.name);
                continue;
            }

            for (auto &sub : fs::directory_iterator(sourceDir))
            {
                std::string symlinkPath = KMUtil::buildFilename(merge_dir, sub.path().filename());
                /* Only create the first, because extensions are listed in prio order */
                if (createdSymlink.end() == createdSymlink.find(symlinkPath))
                {
                    std::string symlink = KMUtil::buildFilename(directory, mergeDir, sub.path().filename());
                    // addArgs({ "--symlink", symlink, symlinkPath });
                    this->symlink(symlink, symlinkPath);
                    createdSymlink.insert(symlinkPath);
                }
            }
        }
    }

    ldPathOut = ldLibraryPath;
    extensionOut = usedExtensions;

    KMTrace("KMExecerWrap::addExtensionArgs invoke end");
    return true;
}

void KMExecerWrap::extendLdPath(const std::string &prepend, const std::string &append)
{
    std::string ldLibraryPath = getEnvs().at("LD_LIBRARY_PATH");

    if (!prepend.empty())
    {
        if (!ldLibraryPath.empty())
        {
            ldLibraryPath = ":" + ldLibraryPath;
        }
        ldLibraryPath = prepend + ldLibraryPath;
    }

    if (!append.empty())
    {
        if (!ldLibraryPath.empty())
        {
            ldLibraryPath.append(":");
        }
        ldLibraryPath.append(append);
    }

    addEnv("LD_LIBRARY_PATH", ldLibraryPath);
}

bool KMExecerWrap::addAppInfoArgs(const std::string &appFiles,
                                  const std::string &originalAppFiles,
                                  const std::string &appCommitId,
                                  const std::string &appExtensions,
                                  const std::string &runtimeFiles,
                                  const std::string &originalRuntimeFiles,
                                  const std::string &runtimeCommitId,
                                  const std::string &runtimeExtensions,
                                  const std::string &appId,
                                  const std::string &appBranch,
                                  const KMRef &runtimeRef,
                                  const std::string &appIdDir,
                                  const std::shared_ptr<KMContext> &finalAppContext,
                                  const std::shared_ptr<KMContext> &cmdlineContext,
                                  bool sandbox,
                                  bool build,
                                  bool devel,
                                  std::string &appInfoPathOut,
                                  int instanceIdFd,
                                  std::string &instanceIdHostDirOut,
                                  std::string &errMsg)
{
    KMTrace("KMExecerWrap::addAppInfoArgs invoke begin");

    if (appId.empty())
    {
        return false;
    }

    if (!d->m_instance)
    {
        d->m_instance = std::make_shared<KMInstance>(appId);
    }

    if (!d->m_instance->ensurePerIdPath())
    {
        errMsg = _("Unable to allocate instance id");
        return false;
    }

    const std::string &instanceIdHostDir = d->m_instance->instanceHostDir();
    const std::string &instanceIdSandboxDir = KMUtil::buildFilename("/run/kaiming/.kaiming", d->m_instance->instanceId());
    const std::string &instanceIdLockFile = KMUtil::buildFilename(instanceIdSandboxDir, ".ref");

    addArgs({ "--ro-bind", instanceIdHostDir, instanceIdSandboxDir, "--lock-file", instanceIdLockFile });
    addRuntimeDirMember(".kaiming");
    /* Keep the .ref lock held until we've started kaiming-box to avoid races */
    addNoinheritFd(d->m_instance->instanceLockFd());

    std::string infoPath = KMUtil::buildFilename(instanceIdHostDir, "info");

    std::shared_ptr<KMMetaData> metadata = std::make_shared<KMMetaData>();
    std::string group(METADATA_GROUP_APPLICATION);
    if (originalAppFiles.empty())
    {
        group = METADATA_GROUP_RUNTIME;
    }

    metadata->setStringValue(group, METADATA_KEY_NAME, appId);
    metadata->setStringValue(group, METADATA_KEY_RUNTIME, KMUtil::dupRef(runtimeRef));

    metadata->setStringValue(METADATA_GROUP_INSTANCE, METADATA_KEY_INSTANCE_ID, d->m_instance->instanceId());
    if (!appIdDir.empty())
    {
        metadata->setStringValue(METADATA_GROUP_INSTANCE, METADATA_KEY_INSTANCE_PATH, appIdDir);
    }
    if (!appFiles.empty())
    {
        metadata->setStringValue(METADATA_GROUP_INSTANCE, METADATA_KEY_APP_PATH, appFiles);
    }
    if (!originalAppFiles.empty() && originalAppFiles != appFiles)
    {
        metadata->setStringValue(METADATA_GROUP_INSTANCE, METADATA_KEY_ORIGINAL_APP_PATH, originalAppFiles);
    }
    if (!appCommitId.empty())
        metadata->setStringValue(METADATA_GROUP_INSTANCE, METADATA_KEY_APP_COMMIT, appCommitId);
    if (!appExtensions.empty())
    {
        metadata->setStringValue(METADATA_GROUP_INSTANCE, METADATA_KEY_APP_EXTENSIONS, appExtensions);
    }
    metadata->setStringValue(METADATA_GROUP_INSTANCE, METADATA_KEY_RUNTIME_PATH, runtimeFiles);
    if (!originalRuntimeFiles.empty() && originalRuntimeFiles != runtimeFiles)
    {
        metadata->setStringValue(METADATA_GROUP_INSTANCE, METADATA_KEY_ORIGINAL_APP_PATH, originalRuntimeFiles);
    }
    if (!runtimeCommitId.empty())
    {
        metadata->setStringValue(METADATA_GROUP_INSTANCE, METADATA_KEY_RUNTIME_COMMIT, runtimeCommitId);
    }
    if (!runtimeExtensions.empty())
    {
        metadata->setStringValue(METADATA_GROUP_INSTANCE, METADATA_KEY_RUNTIME_EXTENSIONS, runtimeExtensions);
    }
    if (!appBranch.empty())
    {
        metadata->setStringValue(METADATA_GROUP_INSTANCE, METADATA_KEY_BRANCH, appBranch);
    }
    metadata->setStringValue(METADATA_GROUP_INSTANCE, METADATA_KEY_ARCH, runtimeRef.arch);

    metadata->setStringValue(METADATA_GROUP_INSTANCE, METADATA_KEY_KAIMING_VERSION, PACKAGE_VERSION);

    if ((finalAppContext->getShares() & KAIMING_CONTEXT_SOCKET_SESSION_BUS) == 0)
    {
        metadata->setBoolValue(METADATA_GROUP_INSTANCE, METADATA_KEY_SESSION_BUS_PROXY, true);
    }

    if ((finalAppContext->getShares() & KAIMING_CONTEXT_SOCKET_SYSTEM_BUS) == 0)
    {
        metadata->setBoolValue(METADATA_GROUP_INSTANCE, METADATA_KEY_SYSTEM_BUS_PROXY, true);
    }

    if (sandbox)
    {
        metadata->setBoolValue(METADATA_GROUP_INSTANCE, METADATA_KEY_SANDBOX, true);
    }

    if (build)
    {
        metadata->setBoolValue(METADATA_GROUP_INSTANCE, METADATA_KEY_BUILD, true);
    }

    if (devel)
    {
        metadata->setBoolValue(METADATA_GROUP_INSTANCE, METADATA_KEY_DEVEL, true);
    }

    if (cmdlineContext)
    {
        // TODO : 暂未使用到，后面遇到相应场景再处理
    }

    finalAppContext->saveMetadata(true, metadata);

    if (!metadata->saveFile(infoPath))
    {
        errMsg = _("Failed to save file: ") + infoPath;
        return false;
    }

    /* We want to create a file on /.kaiming-info that the app cannot modify, which
        we do by creating a read-only bind mount. This way one can openat()
        /proc/$pid/root, and if that succeeds use openat via that to find the
        unfakable .kaiming-info file. However, there is a tiny race in that if
        you manage to open /proc/$pid/root, but then the pid dies, then
        every mount but the root is unmounted in the namespace, so the
        .kaiming-info will be empty. We fix this by first creating a real file
        with the real info in, then bind-mounting on top of that, the same info.
        This way even if the bind-mount is unmounted we can find the real data.
     */
    int fd, fd2, fd3;
    fd = ::open(infoPath.c_str(), O_RDONLY);
    if (fd == -1)
    {
        int errsv = errno;
        errMsg = std::string(_("Failed to open kaiming-info file: ")) + strerror(errsv);
        return false;
    }

    fd2 = ::open(infoPath.c_str(), O_RDONLY);
    if (fd2 == -1)
    {
        ::close(fd);
        int errsv = errno;
        errMsg = std::string(_("Failed to open kaiming-info file: ")) + strerror(errsv);
        return false;
    }
    addArgs({ "--perms", "0600" });
    addArgsDataFd("--file", fd, "/.kaiming-info");
    addArgsDataFd("--ro-bind-data", fd2, "/.kaiming-info");

    /* Tell the application that it's running under kaiming in a generic way. */
    addArgs({ "--setenv", "container", "kaiming" });
    if (!bindMountData("container-manager", "kaiming\n", "/run/host/container-manager"))
    {
        errMsg = _("Failed to bind mount data —— /run/host/container-manager");
        return false;
    }

    std::string boxinfoPath = KMUtil::buildFilename(instanceIdHostDir, "boxinfo.json");
    fd3 = ::open(boxinfoPath.c_str(), O_RDWR | O_CREAT, 0644);
    if (fd3 == -1)
    {
        ::close(fd);
        ::close(fd2);
        int errsv = errno;
        errMsg = std::string(_("Failed to open boxinfo.json file: ")) + strerror(errsv);
        return false;
    }

    /* NOTE: It is important that this takes place after boxinfo.json is created,
             otherwise start notifications in the portal may not work. */
    if (instanceIdFd != -1)
    {
        size_t instanceIdPosition = 0;
        size_t instanceIdSize = d->m_instance->instanceId().length();
        const char *instanceId = d->m_instance->instanceId().c_str();

        while (instanceIdSize > 0)
        {
            size_t bytesWritten = ::write(instanceIdFd, instanceId + instanceIdPosition, instanceIdSize);
            if (bytesWritten <= 0)
            {
                int errsv = bytesWritten == -1 ? errno : ENOSPC;
                if (errsv == EINTR)
                {
                    continue;
                }

                ::close(fd);
                ::close(fd2);
                ::close(fd3);

                errMsg = std::string(_("Failed to write to instance id fd: ")) + strerror(errsv);
                return false;
            }

            instanceIdPosition += bytesWritten;
            instanceIdSize -= bytesWritten;
        }

        ::close(instanceIdFd);
    }

    addArgsDataFd("--info-fd", fd3, "");

    appInfoPathOut = "/proc/self/fd/" + std::to_string(fd);

    instanceIdHostDirOut = instanceIdHostDir;

    KMTrace("KMExecerWrap::addAppInfoArgs invoke end");
    return true;
}

/*
 * @perAppDirLockFd: If >= 0, make use of per-app directories in
 *  the host's XDG_RUNTIME_DIR to share /tmp between instances.
 */
bool KMExecerWrap::addEnvironmentArgs(const std::string& appInfoPath,
                                      KMRunFlags flags,
                                      const std::string& appid,
                                      const std::shared_ptr<KMContext>& context,
                                      const std::string& appIdDir,
                                      const std::vector<std::string>& previousAppIdDirs,
                                      int perAppDirLockFd,
                                      std::shared_ptr<KMExports>& exportsOut,
                                      std::string& errMsg)
{
    KMTrace("KMExecerWrap::addEnvironmentArgs invoke begin");

    if ((context->getShares() & KAIMING_CONTEXT_SHARED_IPC) == 0)
    {
        KMDebug("Disallowing ipc access");
        addArg("--unshare-ipc");
    }

    if ((context->getShares() & KAIMING_CONTEXT_SHARED_NETWORK) == 0)
    {
        KMDebug("Disallowing network access");
        addArg("--unshare-net");
    }

    if (context->getDevices() & KAIMING_CONTEXT_DEVICE_ALL)
    {
        addArgs({ "--dev-bind", "/dev", "/dev" });

        /* 除非明确允许，否则不要公开主机/dev/shm，只公开设备节点 */
        if (fs::is_directory("/dev/shm"))
        {
            if (context->getDevices() & KAIMING_CONTEXT_DEVICE_SHM)
            {
                /* Don't do anything special: include shm in the
                 * shared /dev. The host and all sandboxes and subsandboxes
                 * all share /dev/shm */
            }
            else if ((context->getFeatures() & KAIMING_CONTEXT_FEATURE_PER_APP_DEV_SHM) && perAppDirLockFd >= 0)
            {
                /* The host and the original sandbox have separate /dev/shm,
                 * but we want other instances to be able to share /dev/shm with
                 * the first sandbox (except for subsandboxes run with
                 * kaiming-spawn --sandbox, which will have their own). */
                std::string sharedDevShm;
                if (!KMInstance::ensurePerAppDevShm(appid, perAppDirLockFd, sharedDevShm, errMsg))
                {
                    return false;
                }

                addArgs({ "--bind", sharedDevShm, "/dev/shm" });
            }
            else
            {
                /* 主机、原始沙盒和每个子沙盒都有一个单独的/dev/shm。 */
                addArgs({ "--tmpfs", "/dev/shm" });
            }
        }
        else if (fs::is_symlink("/dev/shm"))
        {
            std::string link = fs::read_symlink("/dev/shm");

            /* On debian (with sysv init) the host /dev/shm is a symlink to /run/shm, so we can't mount on top of it. */
            if (link == "/run/shm")
            {
                if (context->getDevices() & KAIMING_CONTEXT_DEVICE_SHM && fs::is_directory("/run/shm"))
                {
                    addArgs({ "--bind", "/run/shm", "/run/shm" });
                }
                else if ((context->getFeatures() & KAIMING_CONTEXT_FEATURE_PER_APP_DEV_SHM) && perAppDirLockFd >= 0)
                {
                    /* The host and the original sandbox have separate /dev/shm,
                     * but we want other instances to be able to share /dev/shm with
                     * the first sandbox (except for subsandboxes run with
                     * kaiming-spawn --sandbox, which will have their own). */
                    std::string sharedDevShm;
                    if (!KMInstance::ensurePerAppDevShm(appid, perAppDirLockFd, sharedDevShm, errMsg))
                    {
                        return false;
                    }

                    addArgs({ "--bind", sharedDevShm, "/run/shm" });
                }
                else
                {
                    addArgs({ "--dir", "/run/shm" });
                }
            }
            else
            {
                KMDebug("Unexpected /dev/shm symlink " + link);
            }
        }
    }
    else
    {
        addArgs({ "--dev", "/dev" });
        if (context->getDevices() & KAIMING_CONTEXT_DEVICE_DRI)
        {
            KMDebug("Allowing dri access");
            std::vector<std::string> dri_devices{
                "/dev/dri",
                /* mali */
                "/dev/mali",
                "/dev/mali0",
                "/dev/umplock",
                /* nvidia */
                "/dev/nvidiactl",
                "/dev/nvidia-modeset",
                /* nvidia OpenCL/CUDA */
                "/dev/nvidia-uvm",
                "/dev/nvidia-uvm-tools",
            };

            for (auto &device : dri_devices)
            {
                if (fs::exists(device))
                {
                    addArgs({ "--dev-bind", device, device });
                }
            }

            /* Each Nvidia card gets its own device.
             This is a fairly arbitrary limit but ASUS sells mining boards supporting 20 in theory. */
            for (int i = 0; i < 20; ++i)
            {
                std::string nvidiaDev = "/dev/nvidia" + std::to_string(i);
                if (fs::exists(nvidiaDev))
                {
                    addArgs({ "--dev-bind", nvidiaDev, nvidiaDev });
                }
            }
        }

        if (context->getDevices() & KAIMING_CONTEXT_DEVICE_KVM)
        {
            KMDebug("Allowing kvm access");
            if (fs::exists("/dev/kvm"))
            {
                addArgs({ "--dev-bind", "/dev/kvm", "/dev/kvm" });
            }
        }

        if (context->getDevices() & KAIMING_CONTEXT_DEVICE_SHM)
        {
            /* This is a symlink to /run/shm on debian, so bind to real target */
            std::string realDevShm = KMUtil::realpath("/dev/shm");

            KMDebug("Allowing /dev/shm access, as " + realDevShm);
            if (!realDevShm.empty())
            {
                addArgs({ "--bind", realDevShm, "/dev/shm" });
            }
        }
        else if ((context->getFeatures() & KAIMING_CONTEXT_FEATURE_PER_APP_DEV_SHM) && perAppDirLockFd >= 0)
        {
            /* The host and the original sandbox have separate /dev/shm,
             * but we want other instances to be able to share /dev/shm with
             * the first sandbox (except for subsandboxes run with
             * kaiming-spawn --sandbox, which will have their own). */
            std::string sharedDevShm;
            if (!KMInstance::ensurePerAppDevShm(appid, perAppDirLockFd, sharedDevShm, errMsg))
            {
                return false;
            }

            addArgs({ "--bind", sharedDevShm, "/dev/shm" });
        }
    }

    std::string xdgDirsConf;
    bool homeAccess = false;
    std::shared_ptr<KMExports> exports = context->getExportsFull(appIdDir, previousAppIdDirs, true, true, xdgDirsConf, homeAccess);

    if (exports->pathIsVisible("/tmp"))
    {
        /* The original sandbox and any subsandboxes are both already going to share /tmp with the host, so by transitivity they will
         * also share it with each other, and with all other instances. */
    }
    else if (perAppDirLockFd >= 0)
    {
        /* The host and the original sandbox have separate /tmp, but we want other instances to be able to share /tmp with the
         * first sandbox, unless they were created by kaiming-spawn --sandbox.
         *
         * In apply_extra and `kaiming build`, perAppDirLockFd is negative and we skip this. */
        std::string sharedTmp;
        if (!KMInstance::ensurePerAppTmp(appid, perAppDirLockFd, sharedTmp, errMsg))
        {
            return false;
        }

        addArgs({ "--bind", sharedTmp, "/tmp" });
    }

    appendContextFilesystem(context, appid, appIdDir, *exports, xdgDirsConf, homeAccess);

    bool hasWayland = false;
    if (context->getSockets() & KAIMING_CONTEXT_SOCKET_WAYLAND)
    {
        KMDebug("Allowing wayland access");
        hasWayland = addWaylandArgs();
    }

    bool allowX11 = false;
    if ((context->getSockets() & KAIMING_CONTEXT_SOCKET_FALLBACK_X11) != 0)
    {
        allowX11 = !hasWayland;
    }
    else
    {
        allowX11 = (context->getSockets() & KAIMING_CONTEXT_SOCKET_X11) != 0;
    }

    addX11Args(allowX11, context->getShares());

    if (context->getSockets() & KAIMING_CONTEXT_SOCKET_SSH_AUTH)
    {
        addSshArgs();
    }

    if (context->getSockets() & KAIMING_CONTEXT_SOCKET_PULSEAUDIO)
    {
        KMDebug("Allowing pulseaudio access");
        addPulseaudioArgs(context->getShares());
    }

    if (context->getSockets() & KAIMING_CONTEXT_SOCKET_PCSC)
    {
        addPcscArgs();
    }

    if (context->getSockets() & KAIMING_CONTEXT_SOCKET_CUPS)
    {
        addCupsArgs();
    }

    if (context->getSockets() & KAIMING_CONTEXT_SOCKET_GPG_AGENT)
    {
        addGpgAgentArgs();
    }

    std::shared_ptr<KMExecerWrap> proxyArgBwrap = std::make_shared<KMExecerWrap>(std::map<std::string, std::string>());
    addContextSessionDbusArgs(proxyArgBwrap, context, flags, appid);
    addContextSystemDbusArgs(proxyArgBwrap, context, flags);
    addContextA11yDbusArgs(proxyArgBwrap, context, flags);

    if (!proxyArgBwrap->empty() && !startDbusProxy(proxyArgBwrap, appInfoPath))
    {
        return false;
    }

    exportsOut = exports;

    KMTrace("KMExecerWrap::addEnvironmentArgs invoke end");
    return true;
}

void KMExecerWrap::appendContextFilesystem(const std::shared_ptr<KMContext>& context, 
                                            const std::string& appid, 
                                            const std::string& appIdDir, 
                                            const KMExports& exports, 
                                            const std::string& xdgDirsConf, 
                                            bool homeAccess)
{
    KMTrace("KMExecerWrap::appendContextFilesystem invoke begin");

    if (!appIdDir.empty())
    {
        applyEnvAppid(appIdDir);
    }

    if (!homeAccess)
    {
        /* Enable persistent mapping only if no access to real home dir */
        for (auto &pair : context->getPersistent())
        {
            const std::string &persist = pair.first;
            std::string src = KMUtil::buildFilename(g_get_home_dir(), ".var/app", appid, persist);
            std::string dest = KMUtil::buildFilename(g_get_home_dir(), persist);

            if (!KMUtil::mkpath(src, 0755))
            {
                KMDebug("Unable to create directory " + src);
            }

            addBindArg("--bind", src, dest);
        }
    }

    if (!appIdDir.empty())
    {
        std::string userRuntimeDir = KMUtil::getRealXdgRuntimeDir();
        std::string runUserAppDst = KMUtil::buildFilename("/run/kaiming/app", appid);
        std::string runUserAppSrc = KMUtil::buildFilename(userRuntimeDir, "app", appid);

        if (KMUtil::mkpath(runUserAppSrc, 0700))
        {
            addArgs({ "--bind", runUserAppSrc, runUserAppDst });
        }

        /* Later, we'll make $XDG_RUNTIME_DIR/app a symlink to /run/kaiming/app */
        addRuntimeDirMember("app");
    }

    /* This actually outputs the args for the hide/expose operations in the exports */
    appendExportsArgs(exports);

    /* Special case subdirectories of the cache, config and data xdg dirs.  If these are accessible explicitly, then we bind-mount
     * these in the app-id dir. This allows applications to explicitly opt out of keeping some config/cache/data in the app-specific
     * directory.
     */
    if (!appIdDir.empty())
    {
        for (auto &pair : context->getFilesystems())
        {
            const std::string &filesystem = pair.first;
            KMFilesystemMode mode = pair.second;

            std::string rest, where;
            std::string xdgPath = KMContext::getXdgDirFromString(filesystem, rest, where);

            if (!xdgPath.empty() && !rest.empty() && mode >= KAIMING_FILESYSTEM_MODE_READ_ONLY)
            {
                if (fs::is_directory(xdgPath) || fs::is_regular_file(xdgPath))
                {
                    std::string appVersion = KMUtil::buildFilename(appIdDir, where);
                    std::string appVersionSubdir = KMUtil::buildFilename(appVersion, rest);

                    addArgs({ mode == KAIMING_FILESYSTEM_MODE_READ_ONLY ? "--ro-bind" : "--bind", xdgPath, appVersionSubdir });
                }
            }
        }
    }

    if (homeAccess && !appIdDir.empty())
    {
        std::string srcPath = KMUtil::buildFilename(g_get_user_config_dir(), "user-dirs.dirs");
        std::string path = KMUtil::buildFilename(appIdDir, "config/user-dirs.dirs");

        if (fs::exists(srcPath))
        {
            addBindArg("--ro-bind", srcPath, path);
        }
    }
    else if (!xdgDirsConf.empty() && !appIdDir.empty())
    {
        std::string path = KMUtil::buildFilename(appIdDir, "config/user-dirs.dirs");
        bindMountData("xdg-config-dirs", xdgDirsConf, path);
    }

    KMTrace("KMExecerWrap::appendContextFilesystem invoke end");
}

void KMExecerWrap::applyEnvAppid(const std::string &appIdDir)
{
    KMTrace("KMExecerWrap::applyEnvAppid invoke begin");

    std::string appDirData = KMUtil::buildFilename(appIdDir, "data");
    std::string appDirConfig = KMUtil::buildFilename(appIdDir, "config");
    std::string appDirCache = KMUtil::buildFilename(appIdDir, "cache");
    /* Yes, this is inconsistent with data, config and cache. However, using this path lets apps provide backwards-compatibility with older Flatpak
     * versions by using `--persist=.local/state --unset-env=XDG_STATE_DIR`. 开明包也需要兼容一下旧版的flatpak包*/
    std::string appDirState = KMUtil::buildFilename(appIdDir, ".local/state");

    addEnv("XDG_DATA_HOME", appDirData);
    addEnv("XDG_CONFIG_HOME", appDirConfig);
    addEnv("XDG_CACHE_HOME", appDirCache);
    addEnv("XDG_STATE_HOME", appDirState);

    if (getenv("XDG_DATA_HOME"))
    {
        addEnv("HOST_XDG_DATA_HOME", getenv("XDG_DATA_HOME"));
    }
    if (getenv("XDG_CONFIG_HOME"))
    {
        addEnv("HOST_XDG_CONFIG_HOME", getenv("XDG_CONFIG_HOME"));
    }
    if (getenv("XDG_CACHE_HOME"))
    {
        addEnv("HOST_XDG_CACHE_HOME", getenv("XDG_CACHE_HOME"));
    }
    if (getenv("XDG_STATE_HOME"))
    {
        addEnv("HOST_XDG_STATE_HOME", getenv("XDG_STATE_HOME"));
    }

    KMTrace("KMExecerWrap::applyEnvAppid invoke end");
}

static std::string makeRelative(const char *base, const char *path)
{
    std::string s;

    while (*base != 0)
    {
        while (*base == '/')
        {
            base++;
        }

        if (*base != 0)
        {
            s.append("../");
        }

        while (*base != '/' && *base != 0)
        {
            base++;
        }
    }

    while (*path == '/')
    {
        path++;
    }

    return s.append(path);
}

void KMExecerWrap::appendExportsArgs(const KMExports &exports)
{
    KMTrace("KMExecerWrap::appendExportsArgs invoke begin");

    std::vector<std::string> keys;
    const std::map<std::string, ExportedPath> &exportedPaths = exports.getExportedPaths();
    for (auto &pair : exportedPaths)
    {
        keys.push_back(pair.first);
    }
    std::sort(keys.begin(), keys.end());

    KMDebug("Converting KMExports to kaiming-box arguments...");

    for (auto &key : keys)
    {
        const ExportedPath &ep = exportedPaths.at(key);
        assert(KMExports::isExportMode(ep.mode));

        if (ep.mode == FAKE_MODE_SYMLINK)
        {
            KMDebug(ep.path + " is meant to be a symlink");

            if (exports.pathParentIsMapped(keys, ep.path))
            {
                KMDebug(ep.path + " not creating as symlink because its parent is already mapped");
            }
            else
            {
                std::string resolved = exports.resolveLinkInHost(ep.path);

                if (resolved.empty())
                {
                    KMDebug("Unable to resolve \"" + ep.path + "\" in host, skipping");
                }
                else
                {
                    fs::path parent = fs::path(ep.path).parent_path();
                    std::string relative = makeRelative(parent.c_str(), resolved.c_str());

                    KMDebug("Resolved \"" + ep.path + "\" to \"" + resolved + "\" in host");
                    KMDebug("Creating \"" + ep.path + "\" to \"" + resolved + "\" in sandbox");
                    // addArgs({ "--symlink", relative, ep.path });
                    this->symlink(relative, ep.path);
                }
            }
        }
        else if (ep.mode == FAKE_MODE_TMPFS)
        {
            KMDebug(ep.path + " is meant to be a tmpfs or empty directory");
            /* Mount a tmpfs to hide the subdirectory, but only if there is a pre-existing dir we can mount the path on. */
            if (exports.pathIsDir(ep.path))
            {
                if (!exports.pathParentIsMapped(keys, ep.path))
                {
                    /* If the parent is not mapped, it will be a tmpfs, no need to mount another one */
                    KMDebug("Parent of \"" + ep.path + "\" is not mapped, creating empty directory");
                    addArgs({ "--dir", ep.path });
                }
                else
                {
                    KMDebug("Parent of \"" + ep.path + "\" is mapped, creating tmpfs to shadow it");
                    addArgs({ "--tmpfs", ep.path });
                }
            }
            else
            {
                KMDebug("Not a directory, skipping: " + ep.path);
            }
        }
        else if (ep.mode == FAKE_MODE_DIR)
        {
            KMDebug(ep.path + " is meant to be a directory");

            if (exports.pathIsDir(ep.path))
            {
                KMDebug("Ensuring \"" + ep.path + "\" is created as a directory");
                addArgs({ "--dir", ep.path });
            }
            else
            {
                KMDebug("Not a directory, skipping: " + ep.path);
            }
        }
        else
        {
            KMDebug(ep.path + " is meant to be shared (ro or rw) with the container");
            addArgs({ (ep.mode == KAIMING_FILESYSTEM_MODE_READ_ONLY) ? "--ro-bind" : "--bind", ep.path, ep.path });
        }
    }

    assert(exports.hostOs() >= KAIMING_FILESYSTEM_MODE_NONE);
    assert(exports.hostOs() <= KAIMING_FILESYSTEM_MODE_LAST);

    if (exports.hostOs() != KAIMING_FILESYSTEM_MODE_NONE)
    {
        std::string osBindMode("--bind");

        if (exports.hostOs() == KAIMING_FILESYSTEM_MODE_READ_ONLY)
        {
            osBindMode = "--ro-bind";
        }

        struct stat buf;
        if (exports.statInHost("/usr", &buf, 0) && S_ISDIR(buf.st_mode))
        {
            addArgs({ osBindMode, "/usr", "/run/host/usr" });
        }

        /* /usr/local points to ../var/usrlocal on ostree systems, so bind-mount that too. */
        if (exports.statInHost("/var/usrlocal", &buf, 0) && S_ISDIR(buf.st_mode))
        {
            addArgs({ osBindMode, "/var/usrlocal", "/run/host/var/usrlocal" });
        }

        for (auto &dir : d->m_absUsrmergedDirs)
        {
            const std::string &subdir = dir;
            assert(subdir.at(0) == '/');

            /* e.g. /run/host/lib32 */
            std::string runHostSubdir = "/run/host" + subdir;
            std::string target = exports.readlinkInHost(subdir);

            if (!target.empty() && KMUtil::startsWith(target, "usr/"))
            {
                /* e.g. /lib32 is a relative symlink to usr/lib32, or on Arch Linux, /lib64 is a relative symlink to usr/lib; keep it relative */
                // addArgs({ "--symlink", target, runHostSubdir });
                this->symlink(target, runHostSubdir);
            }
            else if (!target.empty() && KMUtil::startsWith(target, "/usr/"))
            {
                /* e.g. /lib32 is an absolute symlink to /usr/lib32; make it a relative symlink to usr/lib32 instead by skipping the '/' */
                // addArgs({ "--symlink", target.substr(1), runHostSubdir });
                this->symlink(target.substr(1), runHostSubdir);
            }
            else if (exports.statInHost(subdir, &buf, 0) && S_ISDIR(buf.st_mode))
            {
                /* e.g. /lib32 is a symlink to /opt/compat/ia32/lib, or is a plain directory because the host OS has not undergone the /usr merge; bind-mount the directory instead */
                addArgs({ osBindMode, subdir, runHostSubdir });
            }
        }

        if (exports.hostEtc() == KAIMING_FILESYSTEM_MODE_NONE)
        {
            /* We are exposing the host /usr (and friends) but not the host /etc. Additionally expose just enough of /etc to make
             * things that want to read /usr work as expected.
             *
             * (If exports->host_etc is nonzero, we'll do this as part of /etc instead.) */
            for (auto &lib : KMExports::m_libsNeedEtc)
            {
                std::string hostPath = "/etc/" + lib.name;

                if (exports.statInHost(hostPath, &buf, 0) && (buf.st_mode & S_IFMT) == lib.ifmt)
                {
                    std::string runHostPath = "/run/host/etc/" + lib.name;
                    addArgs({ osBindMode, hostPath, runHostPath });
                }
            }
        }
    }

    assert(exports.hostEtc() >= KAIMING_FILESYSTEM_MODE_NONE);
    assert(exports.hostEtc() <= KAIMING_FILESYSTEM_MODE_LAST);

    struct stat buf;
    if (exports.hostEtc() != KAIMING_FILESYSTEM_MODE_NONE)
    {
        std::string etcBindMode = "--bind";
        if (exports.hostEtc() == KAIMING_FILESYSTEM_MODE_READ_ONLY)
        {
            etcBindMode = "--ro-bind";
        }

        if (exports.statInHost("/etc", &buf, 0) && S_ISDIR(buf.st_mode))
        {
            addArgs({ etcBindMode, "/etc", "/run/host/etc" });
        }
    }

    /* 根据操作系统发布规范https://www.freedesktop.org/software/systemd/man/os-release.html始终只读绑定挂载/etc/os-release（如果存在），或/usr/lib/os-release作为从主机到应用程序/run/host的回调 */
    if (exports.statInHost("/etc/os-release", &buf, 0))
    {
        addArgs({ "--ro-bind", "/etc/os-release", "/run/host/os-release" });
    }
    else if (exports.statInHost("/usr/lib/os-release", &buf, 0))
    {
        addArgs({ "--ro-bind", "/usr/lib/os-release", "/run/host/os-release" });
    }

    KMTrace("KMExecerWrap::appendExportsArgs invoke end");
}

bool KMExecerWrap::addWaylandArgs()
{
    KMTrace("KMExecerWrap::addWaylandArgs invoke begin");

    const char *waylandDisplay = getenv("WAYLAND_DISPLAY");
    if (!waylandDisplay)
    {
        waylandDisplay = "wayland-0";
    }

    std::string waylandSocket(waylandDisplay);
    std::string userRuntimeDir = KMUtil::getRealXdgRuntimeDir();
    if (waylandDisplay[0] != '/')
    {
        waylandSocket = KMUtil::buildFilename(userRuntimeDir, waylandDisplay);
    }

    if (!KMUtil::startsWith(waylandDisplay, "wayland-") || strchr(waylandDisplay, '/') != nullptr)
    {
        waylandDisplay = "wayland-0";
        addEnv("WAYLAND_DISPLAY", waylandDisplay);
    }

    std::string sandboxWaylandSocket = KMUtil::buildFilename("/run/kaiming", waylandDisplay);

    bool res = false;
    struct stat statbuf;
    if (stat(waylandSocket.c_str(), &statbuf) == 0 && (statbuf.st_mode & S_IFMT) == S_IFSOCK)
    {
        res = true;
        addArgs({ "--ro-bind", waylandSocket, sandboxWaylandSocket });
        addRuntimeDirMember(waylandDisplay);
    }

    KMTrace("KMExecerWrap::addWaylandArgs invoke end");
    return res;
}

void KMExecerWrap::addX11Args(bool allowed, KMContextShares shares)
{
    KMTrace("KMExecerWrap::addX11Args invoke begin");

    /* Always cover /tmp/.X11-unix, that way we never see the host one in case
     * we have access to the host /tmp. If you request X access we'll put the right
     * thing in this anyway.
     *
     * We need to be a bit careful here, because there are two situations in
     * which potentially hostile processes have access to /tmp and could
     * create symlinks, which in principle could cause us to create the
     * directory and mount the tmpfs at the target of the symlink instead
     * of in the intended place:
     *
     * - With --filesystem=/tmp, it's the host /tmp - but because of the
     *   special historical status of /tmp/.X11-unix, we can assume that
     *   it is pre-created by the host system before user code gets to run.
     *
     * - When /tmp is shared between all instances of the same app ID,
     *   in principle the app has control over what's in /tmp, but in
     *   practice it can't interfere with /tmp/.X11-unix, because we do
     *   this unconditionally - therefore by the time app code runs,
     *   /tmp/.X11-unix is already a mount point, meaning the app cannot
     *   rename or delete it.
     */
    addArgs({ "--tmpfs", "/tmp/.X11-unix" });

    if (!allowed)
    {
        removeEnv("DISPLAY");
        return;
    }

    KMDebug("Allowing x11 access");

    std::string display = getenv("DISPLAY") == nullptr ? "" : getenv("DISPLAY");
    std::string x11Socket;
    if (!display.empty())
    {
        std::string remoteHost, displayNr;
        int family = -1;
        if (!parseX11Display(display, family, x11Socket, remoteHost, displayNr))
        {
            removeEnv("DISPLAY");
            return;
        }

        assert(!displayNr.empty());

        if (!x11Socket.empty() && fs::exists(x11Socket))
        {
            assert(KMUtil::startsWith(x11Socket, "/tmp/.X11-unix/X"));
            addArgs({ "--ro-bind", x11Socket, x11Socket });
            addEnv("DISPLAY", display);
        }
        else if ((shares & KAIMING_CONTEXT_SHARED_NETWORK) == 0)
        {
            /* If DISPLAY is for example :42 but /tmp/.X11-unix/X42 doesn't exist, then the only way this is going to work
             * is if the app can connect to abstract socket @/tmp/.X11-unix/X42 or to TCP port localhost:6042,
             * either of which requires a shared network namespace.
             *
             * Alternatively, if DISPLAY is othermachine:23, then we * definitely need access to TCP port othermachine:6023. */
            if (!x11Socket.empty())
            {
                KMWarn("X11 socket " + x11Socket + "does not exist in filesystem.");
            }
            else
            {
                KMWarn("Remote X11 display detected.");
            }
            KMWarn("X11 access will require --share=network permission.");
        }
        else if (!x11Socket.empty())
        {
            KMWarn("X11 socket " + x11Socket + "does not exist in filesystem, trying to use abstract socket instead.");
        }
        else
        {
            KMDebug("Assuming --share=network gives access to remote X11");
        }

#ifdef ENABLE_XAUTH
        KMTmpfile tmpfile;
        std::string errMsg;
        if (KMUtil::openAnonymousTmpfileFull(O_RDWR | O_CLOEXEC, "/tmp", tmpfile, errMsg))
        {
            // std::unique_ptr<std::FILE, int (*)(std::FILE*)> fp(::fdopen(tmpfile.fg.fd(), "wb"), std::fclose);
            FILE *fp = ::fdopen(tmpfile.fg.fd(), "wb");
            if (fp != nullptr)
            {
                /* fd is now owned by output, steal it from the tmpfile */
                int tmpFd = tmpfile.stealFd();
                if (tmpFd != 1)
                {
                    std::string dest = "/run/kaiming/Xauthority";
                    KMXauth::writeXauth(family, remoteHost.c_str(), displayNr.c_str(), fp);
                    addArgsDataFd("--ro-bind-data", tmpFd, dest);
                    addEnv("XAUTHORITY", dest);
                }

                ::fclose(fp);

                if (tmpFd != -1)
                    ::lseek(tmpFd, 0, SEEK_SET);
            }
        }
#endif // !ENABLE_XAUTH
    }
    else
    {
        removeEnv("DISPLAY");
    }

    KMTrace("KMExecerWrap::addX11Args invoke end");
}

bool KMExecerWrap::parseX11Display(const std::string &display, int &family, std::string &x11Socket, std::string &remoteHost, std::string &displayNrOut)
{
    /* Use the last ':', not the first, to cope with [::1]:0 */
    size_t pos = display.find_last_of(':');
    if (std::string::npos == pos)
    {
        KMWarn("No colon found in DISPLAY=" + display);
        return false;
    }

    std::string colon = display.substr(pos);
    if (!isdigit(colon.at(1)))
    {
        KMWarn("Colon not followed by a digit in DISPLAY=" + display);
        return false;
    }

    int nr = 1;
    while (isdigit(colon.at(nr)))
    {
        ++nr;
    }

    displayNrOut = colon.substr(1, nr - 1);

    if (display == colon || KMUtil::startsWith(display, "unix:"))
    {
        family = FamilyLocal;
        x11Socket = "/tmp/.X11-unix/X" + displayNrOut;
    }
    else if (display.at(0) == '[' && display.at(pos - 1) == ']')
    {
        family = FamilyInternet6;
        remoteHost = display.substr(1, pos - 2);
    }
    else
    {
        family = FamilyWild;
        remoteHost = display.substr(0, pos - 1);
    }

    return true;
}

void KMExecerWrap::addSshArgs()
{
    KMTrace("KMExecerWrap::addSshArgs invoke begin");

    std::string sandboxAuthSocket = "/run/kaiming/ssh-auth";
    const char *authSocket = ::getenv("SSH_AUTH_SOCK");

    if (!authSocket)
    {
        return; /* ssh agent not present */
    }

    fs::path authSock(authSocket);
    if (!fs::exists(authSock))
    {
        /* Let's clean it up, so that the application will not try to connect */
        removeEnv("SSH_AUTH_SOCK");
        return;
    }

    addArgs({ "--ro-bind", authSocket, sandboxAuthSocket });
    addEnv("SSH_AUTH_SOCK", sandboxAuthSocket);

    KMTrace("KMExecerWrap::addSshArgs invoke end");
}

void KMExecerWrap::addPulseaudioArgs(KMContextShares shares)
{
    KMTrace("KMExecerWrap::addPulseaudioArgs invoke begin");

    bool remote = false;
    std::string pulseaudioSocket;
    std::string pulseaudioServer = KMExecerWrap::getPulseaudioServer();

    if (!pulseaudioServer.empty())
    {
        pulseaudioSocket = KMExecerWrap::parsePulseServer(pulseaudioServer, remote);
    }

    if (pulseaudioSocket.empty() && !remote)
    {
        std::string pulseRuntimeDir = KMUtil::getPulseRuntimeDir();
        pulseaudioSocket = KMUtil::buildFilename(pulseRuntimeDir, "native");

        if (!fs::exists(pulseaudioSocket))
        {
            pulseaudioSocket = "";
        }
    }

    if (pulseaudioSocket.empty() && !remote)
    {
        pulseaudioSocket = KMUtil::realpath("/var/run/pulse/native");
        if (!pulseaudioSocket.empty() && !fs::exists(pulseaudioSocket))
        {
            pulseaudioSocket = "";
        }
    }

    removeEnv("PULSE_SERVER");

    if (remote)
    {
        if ((shares & KAIMING_CONTEXT_SHARED_NETWORK) == 0)
        {
            KMDebug("Remote PulseAudio server configured.");
            KMDebug("PulseAudio access will require --share=network permission.");
        }

        KMDebug("Using remote PulseAudio server \"" + pulseaudioServer + "\"");
        addEnv("PULSE_SERVER", pulseaudioServer);
    }
    else if (!pulseaudioSocket.empty() && fs::exists(pulseaudioSocket))
    {
        const char *sandboxSocketPath = "/run/kaiming/pulse/native";
        const char *pulseServer = "unix:/run/kaiming/pulse/native";
        const char *configPath = "/run/kaiming/pulse/config";
        bool shareShm = false; /* TODO: When do we add this? */
        std::string clientConfig = std::string("enable-shm=") + (shareShm ? "yes" : "no");
        /* FIXME - error handling */
        if (!bindMountData("pulseaudio", clientConfig, configPath))
        {
            return;
        }

        addArgs({ "--ro-bind", pulseaudioSocket, sandboxSocketPath });
        addEnv("PULSE_SERVER", pulseServer);
        addEnv("PULSE_CLIENTCONFIG", configPath);
        addRuntimeDirMember("pulse");
    }
    else
    {
        KMDebug("Could not find pulseaudio socket");
    }

    /* Also allow ALSA access. This was added in 1.8, and is not ideally named. However, since the practical permission of ALSA and PulseAudio are essentially the same, and
     * since we don't want to add more permissions for something we plan to replace with portals/pipewire going forward we reinterpret pulseaudio to also mean ALSA.
     */

    if (!remote && fs::is_directory(fs::path(std::string("/dev/snd"))))
    {
        addArgs({ "--dev-bind", "/dev/snd", "/dev/snd" });
    }

    KMTrace("KMExecerWrap::addPulseaudioArgs invoke end");
}

std::string KMExecerWrap::parsePulseServer(const std::string &value, bool &remote)
{
    std::vector<std::string> servers = KMUtil::splitString(value, " ");

    for (std::string server : servers)
    {
        if (KMUtil::startsWith(server, "{"))
        {
            /*
             * TODO: compare the value within {} to the local hostname and D-Bus machine ID,
             * and skip if it matches neither.
             */
            size_t pos = server.find_first_of('}');
            if (std::string::npos == pos)
            {
                continue;
            }

            server = server.substr(pos + 1);
        }

        if (KMUtil::startsWith(server, "unix:"))
        {
            return server.substr(5);
        }

        if ('/' == server.at(0))
        {
            return server;
        }

        if (KMUtil::startsWith(server, "tcp:"))
        {
            remote = true;
            return "";
        }
    }

    return "";
}

std::string KMExecerWrap::getPulseaudioServer()
{
    const char *pulseServerEnv = ::getenv("PULSE_SERVER");
    if (pulseServerEnv != nullptr)
    {
        return pulseServerEnv;
    }

    const char *pulseClientconfig = ::getenv("PULSE_CLIENTCONFIG");
    if (pulseClientconfig != nullptr)
    {
        return KMExecerWrap::getPulseaudioServerUserConfig(pulseClientconfig);
    }

    std::string pulseUserConfig = KMUtil::buildFilename(g_get_user_config_dir(), "pulse/client.conf");
    std::string pulseServer = KMExecerWrap::getPulseaudioServerUserConfig(pulseUserConfig);
    if (!pulseServer.empty())
    {
        return pulseServer;
    }

    pulseServer = KMExecerWrap::getPulseaudioServerUserConfig("/etc/pulse/client.conf");
    if (!pulseServer.empty())
    {
        return pulseServer;
    }

    return "";
}

/* Try to find a default server from a pulseaudio confguration file */
std::string KMExecerWrap::getPulseaudioServerUserConfig(const std::string &path)
{
    std::fstream file(path);
    std::string line;

    while (std::getline(file, line))
    {
        line = KMUtil::strstrip(line);
        if (line.empty())
        {
            continue;
        }

        if (line.at(0) == ';' || line.at(0) == '#')
        {
            continue;
        }

        if (KMUtil::startsWith(line, ".include "))
        {
            std::string rec_path = line.substr(9);
            rec_path = KMUtil::strstrip(rec_path);
            std::string found = KMExecerWrap::getPulseaudioServerUserConfig(rec_path);
            if (!found.empty())
            {
                return found;
            }
        }
        else if (KMUtil::startsWith(line, "["))
        {
            return "";
        }
        else
        {
            std::vector<std::string> parts = KMUtil::splitString(line, "=");
            if (parts.size() == 2)
            {
                std::string key = KMUtil::strstrip(parts.at(0));
                if (key == "default-server")
                {
                    std::string value = KMUtil::strstrip(parts.at(1));
                    KMDebug("Found pulseaudio socket from configuration file '" + path + "' : " + value);
                    return value;
                }
            }
        }
    }

    return "";
}

void KMExecerWrap::addPcscArgs()
{
    KMTrace("KMExecerWrap::addPcscArgs invoke begin");

    const char *sandboxPcscSocket = "/run/pcscd/pcscd.comm";

    const char *pcscSocket = getenv("PCSCLITE_CSOCK_NAME");
    if (pcscSocket)
    {
        if (!fs::exists(fs::path(std::string(pcscSocket))))
        {
            removeEnv("PCSCLITE_CSOCK_NAME");
            return;
        }
    }
    else
    {
        pcscSocket = "/run/pcscd/pcscd.comm";
        if (!fs::exists(fs::path(std::string(pcscSocket))))
        {
            return;
        }
    }

    addArgs({ "--ro-bind", pcscSocket, sandboxPcscSocket });
    addEnv("PCSCLITE_CSOCK_NAME", sandboxPcscSocket);

    KMTrace("KMExecerWrap::addPcscArgs invoke end");
}

void KMExecerWrap::addCupsArgs()
{
    KMTrace("KMExecerWrap::addCupsArgs invoke begin");

    std::string sandboxServerName = "/var/run/cups/cups.sock";
    std::string cupsServerName = KMExecerWrap::getCupsServerName();

    if (!fs::exists(cupsServerName))
    {
        KMDebug("Could not find CUPS server");
        return;
    }

    addArgs({ "--ro-bind", cupsServerName, sandboxServerName });

    KMTrace("KMExecerWrap::addCupsArgs invoke end");
}

std::string KMExecerWrap::getCupsServerName()
{
    /* TODO
     * we don't currently support cups servers located on the network, if such
     * server is detected, we simply ignore it and in the worst case we fallback
     * to the default socket
     */
    const char *cupsServer = getenv("CUPS_SERVER");
    if (cupsServer && KMExecerWrap::cupsCheckServerIsSocket(cupsServer))
    {
        return cupsServer;
    }

    std::string cupsConfigPath = KMUtil::buildFilename(g_get_home_dir(), ".cups/client.conf");
    std::string cupsServerString = KMExecerWrap::getCupsServerNameConfig(cupsConfigPath);
    if (!cupsServerString.empty())
    {
        return cupsServerString;
    }

    cupsServerString = KMExecerWrap::getCupsServerNameConfig("/etc/cups/client.conf");
    if (!cupsServerString.empty())
    {
        return cupsServerString;
    }

    // Fallback to default socket
    return "/var/run/cups/cups.sock";
}

bool KMExecerWrap::cupsCheckServerIsSocket(const std::string &server)
{
    if (KMUtil::startsWith(server, "/") && std::string::npos == server.find_first_of(':'))
    {
        return true;
    }

    return false;
}

/* Try to find a default server from a cups confguration file */
std::string KMExecerWrap::getCupsServerNameConfig(const std::string &path)
{
    std::fstream file(path);
    if (file.is_open())
    {
        std::string line;
        while (std::getline(file, line))
        {
            line = KMUtil::strstrip(line);

            if (line.empty() || line.at(0) == '#')
            {
                continue;
            }

            std::vector<std::string> tokens = KMUtil::splitString(line, " ");
            if (tokens.size() != 2)
            {
                continue;
            }

            if (tokens.at(0).empty() || tokens.at(1).empty())
            {
                continue;
            }

            if (tokens.at(0) == "ServerName")
            {
                std::string server = tokens.at(1);
                server = KMUtil::strstrip(server);
                if (KMExecerWrap::cupsCheckServerIsSocket(server))
                {
                    return server;
                }
            }
        }
    }
    else
    {
        KMDebug("Cann't open CUPS configuration file : " + path);
    }

    return "";
}

void KMExecerWrap::addGpgAgentArgs()
{
    KMTrace("KMExecerWrap::addGpgAgentArgs invoke begin");

    std::string agentSocket = KMUtil::execuseCMD("gpgconf", "--list-dir", "agent-socket");
    if (agentSocket.empty() || agentSocket.at(0) != '/')
    {
        KMDebug("'gpgconf --list-dir agent-socket' no result");
        return;
    }

    std::string sandboxAgentSocket = std::string("/run/user/") + std::to_string(getuid()) + "/gnupg/S.gpg-agent";
    addArgs({ "--ro-bind-try", agentSocket, sandboxAgentSocket });

    KMTrace("KMExecerWrap::addGpgAgentArgs invoke end");
}

bool KMExecerWrap::addContextSessionDbusArgs(std::shared_ptr<KMExecerWrap> proxyArgBwrap, const std::shared_ptr<KMContext> &context, KMRunFlags flags, const std::string &appid)
{
    KMTrace("KMExecerWrap::addContextSessionDbusArgs");

    std::string sandboxSocketPath("/run/kaiming/bus");
    std::string sandboxDbusAddress("unix:path=/run/kaiming/bus");
    std::string dbusSessionSocket;

    const char *dbusAddress = getenv("DBUS_SESSION_BUS_ADDRESS");
    if (dbusAddress != nullptr)
    {
        dbusSessionSocket = KMExecerWrap::extractUnixPathFromDbusAddress(dbusAddress);
    }
    else
    {
        std::string userRuntimeDir = KMUtil::getRealXdgRuntimeDir();
        dbusSessionSocket = KMUtil::buildFilename(userRuntimeDir, "bus");

        struct stat statbuf;
        if (::stat(dbusSessionSocket.c_str(), &statbuf) < 0 || (statbuf.st_mode & S_IFMT) != S_IFSOCK || statbuf.st_uid != ::getuid())
        {
            return false;
        }
    }

    bool unrestricted = (context->getSockets() & KAIMING_CONTEXT_SOCKET_SESSION_BUS) != 0;
    if (unrestricted)
    {
        KMDebug("Allowing session-dbus access");
    }

    bool noProxy = (flags & KAIMING_RUN_FLAG_NO_SESSION_BUS_PROXY) != 0;

    if (!dbusSessionSocket.empty() && unrestricted)
    {
        addArgs({ "--ro-bind", dbusSessionSocket, sandboxSocketPath });
        addEnv("DBUS_SESSION_BUS_ADDRESS", sandboxDbusAddress);
        addRuntimeDirMember("bus");

        return true;
    }
    else if (!noProxy && dbusAddress != nullptr)
    {
        std::string proxySocket = KMExecerWrap::createProxySocket("session-bus-proxy-XXXXXX");
        if (proxySocket.empty())
        {
            return false;
        }

        addArgs({ dbusAddress, proxySocket });

        if (!unrestricted)
        {
            proxyArgBwrap->addContextBusFilters(context, appid, true, flags & KAIMING_RUN_FLAG_SANDBOX);

            /* Allow calling any interface+method on all portals, but only receive broadcasts under /org/desktop/portal */
            proxyArgBwrap->addArg("--call=org.freedesktop.portal.*=*");
            proxyArgBwrap->addArg("--broadcast=org.freedesktop.portal.*=@/org/freedesktop/portal/*");
        }

        if ((flags & KAIMING_RUN_FLAG_LOG_SESSION_BUS) != 0)
        {
            proxyArgBwrap->addArg("--log");
        }

        addArgs({ "--ro-bind", proxySocket, sandboxSocketPath });
        addEnv("DBUS_SESSION_BUS_ADDRESS", sandboxDbusAddress);
        addRuntimeDirMember("bus");

        return true;
    }

    return false;
}

std::string KMExecerWrap::extractUnixPathFromDbusAddress(const std::string &address)
{
    if (address.empty())
    {
        return address;
    }

    if (!KMUtil::startsWith(address, "unix:"))
    {
        return "";
    }

    size_t pos = address.find("path=");
    if (std::string::npos == pos)
    {
        return "";
    }

    pos += strlen("path=");

    size_t posEnd = pos;
    while (posEnd < address.length() && address.at(posEnd) != ',')
    {
        ++posEnd;
    }

    return address.substr(pos, posEnd - pos);
}

std::string KMExecerWrap::createProxySocket(const std::string &socket)
{
    std::string proxySocketDir = KMUtil::buildFilename(KMUtil::getRealXdgRuntimeDir(), ".dbus-proxy");
    std::string proxySocket = KMUtil::buildFilename(proxySocketDir, socket);

    if (!KMDirFd::mkdirPAt(AT_FDCWD, proxySocketDir, 0755))
    {
        return "";
    }

    int fd = ::mkstemp((char *)proxySocket.c_str());
    if (fd == -1)
    {
        return "";
    }

    ::close(fd);

    return proxySocket;
}

void KMExecerWrap::addContextBusFilters(const std::shared_ptr<KMContext>& context,
                                        const std::string& appid,
                                        bool sessionBus,
                                        bool sandboxed)
{
    addArg("--filter");
    if (!appid.empty() && sessionBus)
    {
        std::string filter;
        if (sandboxed)
        {
            filter = "--own=" + appid + ".Sandboxed.*";
            addArg(filter);

            filter = "--own=org.mpris.MediaPlayer2." + appid + ".Sandboxed.*";
            addArg(filter);
        }
        else
        {
            filter = "--own=" + appid + ".*";
            addArg(filter);

            filter = "--own=org.mpris.MediaPlayer2." + appid + ".*";
            addArg(filter);
        }
    }

    std::map<std::string, KMPolicy> policyMap;
    if (sessionBus)
    {
        policyMap = context->getSessionBusPolicy();
    }
    else
    {
        policyMap = context->getSystemBusPolicy();
    }

    std::string filter;
    for (const auto &pair : policyMap)
    {
        KMPolicy policy = pair.second;
        if (policy > 0)
        {
            filter = "--" + KMContext::policy2string(policy) + "=" + pair.first;
            addArg(filter);
        }
    }
}

bool KMExecerWrap::addContextSystemDbusArgs(std::shared_ptr<KMExecerWrap> proxyArgBwrap,
                                            const std::shared_ptr<KMContext>& context,
                                            KMRunFlags flags)
{
    KMTrace("KMExecerWrap::addContextSystemDbusArgs");

    bool unrestricted = (context->getSockets() & KAIMING_CONTEXT_SOCKET_SYSTEM_BUS) != 0;
    if (unrestricted)
    {
        KMDebug("Allowing system-dbus access");
    }

    std::string dbusSystemSocket;
    std::string dbusSystemSocketString("/var/run/dbus/system_bus_socket");
    const char *dbusAddress = getenv("DBUS_SYSTEM_BUS_ADDRESS");
    if (dbusAddress != nullptr)
    {
        dbusSystemSocket = KMExecerWrap::extractUnixPathFromDbusAddress(dbusAddress);
    }
    else if (fs::exists(dbusSystemSocketString))
    {
        dbusSystemSocket = dbusSystemSocketString;
    }

    bool noProxy = (flags & KAIMING_RUN_FLAG_NO_SYSTEM_BUS_PROXY) != 0;
    std::string realDbusAddress;
    if (!dbusSystemSocket.empty() && unrestricted)
    {
        addArgs({ "--ro-bind", dbusSystemSocket, "/run/dbus/system_bus_socket" });
        addEnv("DBUS_SYSTEM_BUS_ADDRESS", "unix:path=/run/dbus/system_bus_socket");

        return true;
    }
    else if (!noProxy && context->getNeedsSystemBusProxy())
    {
        std::string proxySocket = KMExecerWrap::createProxySocket("system-bus-proxy-XXXXXX");
        if (proxySocket.empty())
        {
            return false;
        }

        if (dbusAddress != nullptr)
        {
            realDbusAddress = dbusAddress;
        }
        else
        {
            realDbusAddress = "unix:path=" + dbusSystemSocket;
        }

        proxyArgBwrap->addArgs({ realDbusAddress, proxySocket });

        if (!unrestricted)
        {
            proxyArgBwrap->addContextBusFilters(context, "", false, flags & KAIMING_RUN_FLAG_SANDBOX);
        }

        if ((flags & KAIMING_RUN_FLAG_LOG_SYSTEM_BUS) != 0)
        {
            proxyArgBwrap->addArg("--log");
        }

        addArgs({ "--ro-bind", proxySocket, "/run/dbus/system_bus_socket" });
        addEnv("DBUS_SYSTEM_BUS_ADDRESS", "unix:path=/run/dbus/system_bus_socket");

        return true;
    }

    return false;
}

bool KMExecerWrap::addContextA11yDbusArgs(std::shared_ptr<KMExecerWrap> proxyArgBwrap, const std::shared_ptr<KMContext> &context, KMRunFlags flags)
{
    KMTrace("KMExecerWrap::addContextA11yDbusArgs");

    if ((flags & KAIMING_RUN_FLAG_NO_A11Y_BUS_PROXY) != 0)
    {
        return false;
    }

    DBus::Connection connection = DBus::Connection::SessionBus();
    KMDBusProxy proxy(connection, "/org/a11y/bus", "org.a11y.Bus");

    DBus::CallMessage call("org.a11y.Bus", "/org/a11y/bus", "org.a11y.Bus", "GetAddress");
    DBus::Message ret = proxy.invoke_method(call);
    std::string a11yAddress;
    DBus::MessageIter ri = ret.reader();
    if (!ri.at_end())
    {
        a11yAddress = ri.get_string();
    }

    if (a11yAddress.empty())
    {
        return false;
    }

    std::string proxySocket = KMExecerWrap::createProxySocket("a11y-bus-proxy-XXXXXX");
    if (proxySocket.empty())
    {
        return false;
    }

    proxyArgBwrap->addArgs({a11yAddress,
                            proxySocket, "--filter", "--sloppy-names",
                            "--call=org.a11y.atspi.Registry=org.a11y.atspi.Socket.Embed@/org/a11y/atspi/accessible/root",
                            "--call=org.a11y.atspi.Registry=org.a11y.atspi.Socket.Unembed@/org/a11y/atspi/accessible/root",
                            "--call=org.a11y.atspi.Registry=org.a11y.atspi.Registry.GetRegisteredEvents@/org/a11y/atspi/registry",
                            "--call=org.a11y.atspi.Registry=org.a11y.atspi.DeviceEventController.GetKeystrokeListeners@/org/a11y/atspi/registry/deviceeventcontroller",
                            "--call=org.a11y.atspi.Registry=org.a11y.atspi.DeviceEventController.GetDeviceEventListeners@/org/a11y/atspi/registry/deviceeventcontroller",
                            "--call=org.a11y.atspi.Registry=org.a11y.atspi.DeviceEventController.NotifyListenersSync@/org/a11y/atspi/registry/deviceeventcontroller",
                            "--call=org.a11y.atspi.Registry=org.a11y.atspi.DeviceEventController.NotifyListenersAsync@/org/a11y/atspi/registry/deviceeventcontroller",
                            });

    if ((flags & KAIMING_RUN_FLAG_LOG_A11Y_BUS) != 0)
    {
        proxyArgBwrap->addArg("--log");
    }

    addArgs({ "--ro-bind", proxySocket, "/run/kaiming/at-spi-bus" });
    addEnv("AT_SPI_BUS_ADDRESS", "unix:path=/run/kaiming/at-spi-bus");

    return true;
}

bool KMExecerWrap::startDbusProxy(std::shared_ptr<KMExecerWrap> proxyArgBwrap, const std::string &app_info_path)
{
    // TODO : 启动kaiming-dbus-proxy应用程序，通过此程序开启某服务(g_socket_service_start)，此处计划用于开启某dbus服务(如：xdg-dbus-proxy)。开发者构建程序时暂时不需要，先不提供。
    KMDebug("Functin 'startDbusProxy' has not been implemented.");
    return true;
}

static void expectSymlink(const std::string &hostPath, const std::string &target)
{
    if (::symlink(target.c_str(), hostPath.c_str()) < 0 && errno != EEXIST)
    {
        KMWarn("Unable to create symlink at " + hostPath + " : " + g_strerror(errno));
    }
    else
    {
        std::string link = KMDirFd::readlinkat(AT_FDCWD, hostPath);
        if (link.empty())
        {
            KMWarn(hostPath + " is not a symlink to " + target);
        }
        else if (link != target)
        {
            KMWarn(hostPath + " is a symlink to " + link + ", not " + target);
        }
    }
}

void KMExecerWrap::populateRuntimeDir(const std::string &sharedXdgRuntimeDir)
{
    KMTrace("KMExecerWrap::populateRuntimeDir invoke begin");

    if (!sharedXdgRuntimeDir.empty())
    {
        std::string hostPath = KMUtil::buildFilename(sharedXdgRuntimeDir, "kaiming-info");

        expectSymlink(hostPath, "../../../.kaiming-info");
    }
    else
    {
        std::string hostPath = "/run/user/" + std::to_string(getuid()) + "/kaiming-info";

        // addArgs({ "--symlink", "../../../.kaiming-info", hostPath });
        this->symlink("../../../.kaiming-info", hostPath);
    }

    for (const std::string &runtimeDir : getRuntimeDirMember())
    {
        std::string target = "../../kaiming/" + runtimeDir;

        if (!sharedXdgRuntimeDir.empty())
        {
            std::string hostPath = KMUtil::buildFilename(sharedXdgRuntimeDir, runtimeDir);

            expectSymlink(hostPath, target);
        }
        else
        {
            std::string hostPath = "/run/user/" + std::to_string(getuid()) + "/" + runtimeDir;
            KMUtil::replace(hostPath, "//", "/");

            // addArgs({ "--symlink", target, hostPath });
            this->symlink(target, hostPath);
        }
    }

    KMTrace("KMExecerWrap::populateRuntimeDir invoke end");
}

void KMExecerWrap::envpToArgs()
{
    KMTrace("KMExecerWrap::envpToArgs invoke begin");

    for (auto &pair : getEnvs())
    {
        addArgs({ "--setenv", pair.first, pair.second });
    }

    clearEnvs();

    KMTrace("KMExecerWrap::envpToArgs invoke end");
}

bool KMExecerWrap::bundleArgs(int start, int end, bool oneArg)
{
    KMTrace("KMExecerWrap::bundleArgs invoke begin");

    const std::vector<std::string> &args = getArgs();
    if (end == -1)
    {
        end = args.size();
    }

    size_t dataLen = 0;
    for (int i = start; i < end; ++i)
    {
        dataLen += args.at(i).length() + 1;
    }

    char *data = new char[dataLen];
    std::unique_ptr<char[]> dataPtr(data);
    memset(data, 0, dataLen);

    char *ptr = data;
    for (int i = start; i < end; i++)
    {
        ptr = ::stpcpy(ptr, args.at(i).c_str()) + 1;
    }

    KMTmpfile argsTmpf;
    std::string errMsg;
    if (!KMUtil::sealedMemfdOrTmpfile(argsTmpf, "box-args", data, dataLen, errMsg))
    {
        KMError(errMsg);
        return false;
    }

    int fd = argsTmpf.stealFd();
    KMDebug("kaiming-box --args " + std::to_string(fd) + " = ...");

    std::string cmdline;
    char *buf = new char[1024];
    std::unique_ptr<char[]> bufPtr(buf);
    for (int i = start; i < end; i++)
    {
        memset(buf, 0, 1024);
        const std::string &arg = args.at(i);
        if (KMUtil::argumentNeedsQuoting(arg.c_str()))
        {
            g_autofree char *quoted = g_shell_quote(arg.c_str());
            snprintf(buf, 1024, "    %s\n", quoted);
        }
        else
        {
            snprintf(buf, 1024, "    %s\n", arg.c_str());
        }

        cmdline += buf;
    }
    KMDebug(cmdline);

    addFd(fd);

    removeRangeArgs(start, end);

    if (oneArg)
    {
        memset(buf, 0, 1024);
        snprintf(buf, 1024, "--args=%d", fd);
        insertArg(start, buf);
    }
    else
    {
        insertArg(start, "--args");
        insertArg(start + 1, std::to_string(fd));
    }

    KMTrace("KMExecerWrap::bundleArgs invoke end");
    return true;
}

void KMExecerWrap::setupChildFds()
{
    const std::vector<int> &fds = getFds();

    /* Mark not - close-on-exec all the fds in the array */
    for (int fd : fds)
    {
        ::fcntl(fd, F_SETFD, 0);
    }
}
