#include "KMContext.h"

#include <glib.h>
#include <vector>
#include <fcntl.h>

#include "KMLogger.h"
#include "KMStorageDir.h"
#include "common/KMUtil.h"
#include "kmtranslation.h"

static guint32 bitmaskFromString(const char *name, const char **names)
{
    guint32 i;

    for (i = 0; names[i] != nullptr; i++)
    {
        if (strcmp(names[i], name) == 0)
        {
            return 1 << i;
        }
    }

    return 0;
}

static std::vector<std::string> bitmaskToString(guint32 enabled, guint32 valid, const char **names)
{
    std::vector<std::string> arr;
    for (guint32 i = 0; names[i] != NULL; i++)
    {
        guint32 bitmask = 1 << i;
        if (valid & bitmask)
        {
            if (enabled & bitmask)
            {
                arr.push_back(names[i]);
            }
            else
            {
                arr.push_back(std::string("!") + names[i]);
            }
        }
    }

    return arr;
}

static const char *kaimingContextShares[] = { "network", "ipc", nullptr };

static const char *kaimingContextSockets[] = { "x11", "wayland", "pulseaudio", "session-bus", "system-bus", "fallback-x11", "ssh-auth", "pcsc", "cups", "gpg-agent", nullptr };

static const char *kaimingContextDevices[] = { "dri", "all", "kvm", "shm", nullptr };

static const char *kaimingContextFeatures[] = { "devel", "multiarch", "bluetooth", "canbus", "per-app-dev-shm", nullptr };

// static const char *kaimingContextSpecialFilesystems[] = {
//     "home",
//     "host",
//     "host-etc",
//     "host-os",
//     "host-reset",
//     nullptr
// };

static std::vector<std::string> kaimingContextSpecialFilesystems{ "home", "host", "host-etc", "host-os", "host-reset" };

static std::vector<std::string> dontMountInRoot = { ".", "..", "app", "bin", "boot", "dev", "efi", "etc", "lib", "lib32", "lib64", "proc", "root", "run", "sbin", "sys", "tmp", "usr", "var", "backup" };

KMContext::KMContext()
{
    resetPermissions();
}

KMContext::~KMContext() { }

bool KMContext::loadMetadata(KMMetaData *metadata)
{
    bool remove;
    for (auto shareInt : metadata->getShared())
    {
        if (shareInt.length())
        {
            remove = shareInt.at(0) == '!';
            if (remove)
            {
                shareInt = shareInt.substr(1);
            }

            KMContextShares share = fromString<KMContextShares>(shareInt, kaimingContextShares);
            if (share == 0)
            {
                kmlogger.info("unknow share type %s\n", shareInt.c_str());
                continue;
            }

            if (remove)
            {
                removeShares(share);
            }
            else
            {
                addShares(share);
            }
        }
    }

    for (auto socketInt : metadata->getSockets())
    {
        if (socketInt.length())
        {
            remove = socketInt.at(0) == '!';
            if (remove)
            {
                socketInt = socketInt.substr(1);
            }

            KMContextSockets socket = fromString<KMContextSockets>(socketInt, kaimingContextSockets);
            if (socket == 0)
            {
                kmlogger.info("unknow socket type %s\n", socketInt.c_str());
                continue;
            }
            else if (socket == KAIMING_CONTEXT_SOCKET_FALLBACK_X11)
            {
                socket = (KMContextSockets)(socket | KAIMING_CONTEXT_SOCKET_X11);
            }

            if (remove)
            {
                removeSockets(socket);
            }
            else
            {
                addSockets(socket);
            }
        }
    }

    for (auto deviceInt : metadata->getDevices())
    {
        if (deviceInt.length())
        {
            remove = deviceInt.at(0) == '!';
            if (remove)
            {
                deviceInt = deviceInt.substr(1);
            }

            KMContextDevices device = fromString<KMContextDevices>(deviceInt, kaimingContextDevices);
            if (device == 0)
            {
                kmlogger.info("unknow socket type %s\n", deviceInt.c_str());
                continue;
            }

            if (remove)
            {
                removeDevices(device);
            }
            else
            {
                addDevices(device);
            }
        }
    }

    for (auto featureInt : metadata->getFeatures())
    {
        if (featureInt.length())
        {
            remove = featureInt.at(0) == '!';
            if (remove)
            {
                featureInt = featureInt.substr(1);
            }

            KMContextFeatures feature = fromString<KMContextFeatures>(featureInt, kaimingContextFeatures);
            if (feature == 0)
            {
                kmlogger.info("unknow feature type %s\n", featureInt.c_str());
                continue;
            }

            if (remove)
            {
                removeFeatures(feature);
            }
            else
            {
                addFeatures(feature);
            }
        }
    }

    for (auto filesystem_i : metadata->getFilesystem())
    {
        if (filesystem_i.length())
        {
            remove = filesystem_i.at(0) == '!';
            if (remove)
            {
                filesystem_i = filesystem_i.substr(1);
            }

            std::string filesystem;
            KMFilesystemMode mode;
            if (parseFilesystem(filesystem_i, remove, filesystem, mode))
            {
                assert(mode == KAIMING_FILESYSTEM_MODE_NONE || !remove);
                takeFilesystem(filesystem, mode);
            }
            else
            {
                KMError(filesystem_i + " is wrong filesystem type");
                return false;
            }
        }
    }

    for (auto persistentInt : metadata->getPersistent())
    {
        if (persistentInt.empty())
        {
            continue;
        }

        addPersistent(persistentInt, 1);
    }

    for (auto pairInt : metadata->getSessionBusPolicy())
    {
        string key = pairInt.first;
        string val = pairInt.second;
        if (val == "none")
        {
            m_sessionBusPolicy[key] = KAIMING_POLICY_NONE;
        }
        else if (val == "see")
        {
            m_sessionBusPolicy[key] = KAIMING_POLICY_SEE;
        }
        else if (val == "talk")
        {
            m_sessionBusPolicy[key] = KAIMING_POLICY_TALK;
        }
        else if (val == "own")
        {
            m_sessionBusPolicy[key] = KAIMING_POLICY_OWN;
        }
    }

    for (auto pairInt : metadata->getSystemBusPolicy())
    {
        string key = pairInt.first;
        string val = pairInt.second;
        if (val == "none")
        {
            m_systemBusPolicy[key] = KAIMING_POLICY_NONE;
        }
        else if (val == "see")
        {
            m_systemBusPolicy[key] = KAIMING_POLICY_SEE;
        }
        else if (val == "talk")
        {
            m_systemBusPolicy[key] = KAIMING_POLICY_TALK;
        }
        else if (val == "own")
        {
            m_systemBusPolicy[key] = KAIMING_POLICY_OWN;
        }
    }

    m_envVars.insert(metadata->getEnvironment().begin(), metadata->getEnvironment().end());

    for (auto unsetEnv : metadata->getUnsetEnvironment())
    {
        m_envVars[unsetEnv] = "";
    }

    for (const auto &policyPair : metadata->getGenericPolicy())
    {
        std::string policyKey = policyPair.first;
        if (!policyKey.empty())
        {
            m_genericPolicy.insert(policyPair);
        }
    }
    // m_genericPolicy = metadata->getGenericPolicy();

    return true;
}

unique_ptr<KMExports> KMContext::createExports(const fs::path &appPath, const vector<fs::path> &extraAppIdPaths, bool create, string &xdgDirConfOut)
{
    bool homeAccess = false;
    string xdg_dir_out;
    KMFilesystemMode fsMode = KAIMING_FILESYSTEM_MODE_NONE;

    if (xdgDirConfOut.length() > 0)
    {
        xdg_dir_out = "";
    }

    if (m_filesystems.find("host") != m_filesystems.end())
    {
        fsMode = m_filesystems["host"];
        if (fsMode != KAIMING_FILESYSTEM_MODE_NONE)
        {
            homeAccess = true;
        }
    }

    return unique_ptr<KMExports>();
}

template<typename T>
T KMContext::fromString(const string &str, const char **names)
{
    return (T)bitmaskFromString(str.data(), names);
}

string KMContext::policy2string(KMPolicy policy)
{
    if (policy == KAIMING_POLICY_SEE)
    {
        return "see";
    }

    if (policy == KAIMING_POLICY_TALK)
    {
        return "talk";
    }
    
    if (policy == KAIMING_POLICY_OWN)
    {
        return "own";
    }

    return "none";
}

void KMContext::addShares(KMContextShares shares)
{
    m_sharesValid = (KMContextShares)(m_sharesValid | shares);
    m_shares = (KMContextShares)(m_shares | shares);
}

void KMContext::removeShares(KMContextShares shares)
{
    m_sharesValid = (KMContextShares)(m_sharesValid | shares);
    m_shares = (KMContextShares)(m_shares & ~shares);
}

void KMContext::addSockets(KMContextSockets socket)
{
    m_socketsValid = (KMContextSockets)(m_socketsValid | socket);
    m_sockets = (KMContextSockets)(m_sockets | socket);
}

void KMContext::removeSockets(KMContextSockets socket)
{
    m_socketsValid = (KMContextSockets)(m_socketsValid | socket);
    m_sockets = (KMContextSockets)(m_sockets & ~socket);
}

void KMContext::addDevices(KMContextDevices device)
{
    m_devicesValid = (KMContextDevices)(m_devicesValid | device);
    m_devices = (KMContextDevices)(m_devices | device);
}

void KMContext::removeDevices(KMContextDevices device)
{
    m_devicesValid = (KMContextDevices)(m_devicesValid | device);
    m_devices = (KMContextDevices)(m_devices & ~device);
}

void KMContext::addFeatures(KMContextFeatures feature)
{
    m_featuresValid = (KMContextFeatures)(m_featuresValid | feature);
    m_features = (KMContextFeatures)(m_features | feature);
}

void KMContext::removeFeatures(KMContextFeatures feature)
{
    m_featuresValid = (KMContextFeatures)(m_featuresValid | feature);
    m_features = (KMContextFeatures)(m_features & ~feature);
}

void KMContext::addPersistent(const string &path, int val)
{
    m_persistent[path] = val;
}

std::string KMContext::parseFilesystemFlags(const string &filesystemAndMode, bool cancel, KMFilesystemMode &outMode)
{
    std::string filesystem;
    KMFilesystemMode mode;

    const char *p = filesystemAndMode.c_str();
    while (*p != 0 && *p != ':')
    {
        if (*p == '\\')
        {
            p++;
            if (*p != 0)
            {
                filesystem += *p++;
            }
        }
        else
        {
            filesystem += *p++;
        }
    }

    if (cancel)
    {
        mode = KAIMING_FILESYSTEM_MODE_NONE;
    }
    else
    {
        mode = KAIMING_FILESYSTEM_MODE_READ_WRITE;
    }

    bool reset = false;
    if (filesystem == "host-reset")
    {
        reset = true;

        if (!cancel)
        {
            KMError("Filesystem token \"host-reset\" is only applicable for --nofilesystem");
            return "";
        }

        if (*p != '\0')
        {
            KMError("Filesystem token \"host-reset\" cannot be used with a suffix");
            return "";
        }
    }

    std::string suffix;
    if (*p == ':')
    {
        suffix = p + 1;

        if (suffix == "ro")
        {
            mode = KAIMING_FILESYSTEM_MODE_READ_ONLY;
        }
        else if (suffix == "rw")
        {
            mode = KAIMING_FILESYSTEM_MODE_READ_WRITE;
        }
        else if (suffix == "create")
        {
            mode = KAIMING_FILESYSTEM_MODE_CREATE;
        }
        else if (suffix == "reset")
        {
            reset = true;
        }
        else if (suffix.length() > 0)
        {
            KMWarn(suffix + " is unexpected filesystem suffix, ignoring");
        }

        if (cancel && mode != KAIMING_FILESYSTEM_MODE_NONE)
        {
            KMWarn(suffix + " is not applicable filesystem suffix for --nofilesystem");
            mode = KAIMING_FILESYSTEM_MODE_NONE;
        }

        if (reset)
        {
            if (!cancel)
            {
                KMError(suffix + " is filesystem suffix that only applies to --nofilesystem");
                return "";
            }

            if (filesystem != "host")
            {
                KMError(suffix + " is filesystem suffix that can only applies to --nofilesystem=host");
                return "";
            }

            /* We internally handle host:reset (etc) as host-reset, only exposing it as a flag in the public
               part to allow it to be ignored (with a warning) for old kaiming versions */
            filesystem += "-reset";
        }
    }

    /* Postcondition check: the code above should make some results
     * impossible */
    if (cancel)
    {
        assert(mode == KAIMING_FILESYSTEM_MODE_NONE);
    }
    else
    {
        assert(mode > KAIMING_FILESYSTEM_MODE_NONE);
        /* This flag is only applicable to --nofilesystem */
        assert(!reset);
    }

    /* Postcondition check: filesystem token is host-reset iff reset flag
     * was found */
    if (reset)
    {
        assert(filesystem == "host-reset");
    }
    else
    {
        assert(filesystem != "host-reset");
    }

    outMode = mode;

    return filesystem;
}

std::string KMContext::unparseFilesystemFlags(const std::string &path, KMFilesystemMode mode)
{
    std::string s;

    for (const char *p = path.c_str(); *p != 0; p++)
    {
        if (*p == ':')
        {
            s.append("\\:");
        }
        else if (*p == '\\')
        {
            s.append("\\\\");
        }
        else
        {
            s += *p;
        }
    }

    switch (mode)
    {
    case KAIMING_FILESYSTEM_MODE_READ_ONLY:
        s.append(":ro");
        break;

    case KAIMING_FILESYSTEM_MODE_CREATE:
        s.append(":create");
        break;

    case KAIMING_FILESYSTEM_MODE_READ_WRITE:
        break;

    case KAIMING_FILESYSTEM_MODE_NONE:
        s += '!';

        if (KMUtil::endsWith(s, "-reset"))
        {
            KMUtil::replace(s, "-reset", "");
            s.append(":reset");
        }
        break;

    default:
        KMWarn("Unexpected filesystem mode");
        break;
    }

    return s;
}

bool KMContext::getXdgUserDirFromString(const std::string &filesystem, std::string &configKey, std::string &suffix, std::string &dir)
{
    std::size_t pos = filesystem.find("/");
    int len = 0;
    std::string rest;
    if (std::string::npos == pos)
    {
        len = filesystem.length();
    }
    else
    {
        len = pos;
        rest = filesystem.substr(pos + 1);
        while (KMUtil::startsWith(rest, "/"))
        {
            rest = rest.substr(1);
        }
    }

    suffix = rest;

    std::string prefix = filesystem.substr(0, len);

    if (prefix == "xdg-desktop")
    {
        configKey = "XDG_DESKTOP_DIR";
        dir = g_get_user_special_dir(G_USER_DIRECTORY_DESKTOP);

        return true;
    }

    if (prefix == "xdg-documents")
    {
        configKey = "XDG_DOCUMENTS_DIR";
        dir = g_get_user_special_dir(G_USER_DIRECTORY_DOCUMENTS);

        return true;
    }

    if (prefix == "xdg-download")
    {
        configKey = "XDG_DOWNLOAD_DIR";
        dir = g_get_user_special_dir(G_USER_DIRECTORY_DOWNLOAD);

        return true;
    }

    if (prefix == "xdg-music")
    {
        configKey = "XDG_MUSIC_DIR";
        dir = g_get_user_special_dir(G_USER_DIRECTORY_MUSIC);

        return true;
    }

    if (prefix == "xdg-pictures")
    {
        configKey = "XDG_PICTURES_DIR";
        dir = g_get_user_special_dir(G_USER_DIRECTORY_PICTURES);

        return true;
    }

    if (prefix == "xdg-public-share")
    {
        configKey = "XDG_PUBLICSHARE_DIR";
        dir = g_get_user_special_dir(G_USER_DIRECTORY_PUBLIC_SHARE);

        return true;
    }

    if (prefix == "xdg-templates")
    {
        configKey = "XDG_TEMPLATES_DIR";
        dir = g_get_user_special_dir(G_USER_DIRECTORY_TEMPLATES);

        return true;
    }

    if (prefix == "xdg-videos")
    {
        configKey = "XDG_VIDEOS_DIR";
        dir = g_get_user_special_dir(G_USER_DIRECTORY_VIDEOS);

        return true;
    }

    std::string where;
    if (getXdgDirFromPrefix(prefix, where, dir))
    {
        configKey = "";

        return true;
    }

    /* Don't support xdg-run without suffix, because that doesn't work */
    if (prefix == "xdg-run" && !rest.empty())
    {
        configKey = "";
        dir = KMUtil::realpath(g_get_user_runtime_dir());

        return true;
    }

    return false;
}

bool KMContext::getXdgDirFromPrefix(const std::string &prefix, std::string &where, std::string &dir)
{
    if (prefix == "xdg-data")
    {
        where = "data";
        dir = g_get_user_data_dir();

        return true;
    }

    if (prefix == "xdg-cache")
    {
        where = "cache";
        dir = g_get_user_cache_dir();

        return true;
    }

    if (prefix == "xdg-config")
    {
        where = "config";
        dir = g_get_user_config_dir();

        return true;
    }

    return false;
}

std::string KMContext::getXdgDirFromString(const std::string &filesystem, std::string &suffix, std::string &where)
{
    const char *filesystemString = filesystem.c_str();
    const char *slash = strchr(filesystemString, '/');

    size_t len = 0;
    if (slash)
    {
        len = slash - filesystemString;
    }
    else
    {
        len = strlen(filesystemString);
    }

    const char *rest = filesystemString + len;
    while (*rest == '/')
    {
        rest++;
    }

    suffix = rest;

    std::string prefix = filesystem.substr(0, len);

    std::string dir;
    if (getXdgDirFromPrefix(prefix, where, dir))
    {
        return KMUtil::buildFilename(dir, suffix);
    }

    return "";
}

bool KMContext::parseFilesystem(const string &filesystemAndMode, bool cancel, string &filesystem_out, KMFilesystemMode &mode)
{
    // 字符串编码默认都是utf-8，暂不做校验功能，如果需要后面再加：validate_path_characters

    std::string filesystem = parseFilesystemFlags(filesystemAndMode, cancel, mode);
    if (filesystem.empty())
    {
        return false;
    }

    /* Forbid /../ in paths */
    if (KMUtil::contains(filesystem, "/../") || KMUtil::endsWith(filesystem, "/.."))
    {
        KMError(filesystem + ", filesystem location contains \"..\"");
        return false;
    }

    /* Convert "//" and "/./" to "/" */
    KMUtil::replace(filesystem, "//", "/");
    KMUtil::replace(filesystem, "/./", "/");

    /* Eliminate trailing "/." or "/". */
    if (filesystem == "/")
    {
        KMError("--filesystem=/ is not available, use --filesystem=host for a similar result");
        return false;
    }
    if (KMUtil::endsWith(filesystem, "/."))
    {
        filesystem = filesystem.substr(0, filesystem.length() - 2);
    }
    if (KMUtil::endsWith(filesystem, "/"))
    {
        filesystem = filesystem.substr(0, filesystem.length() - 1);
    }

    std::string configKey;
    std::string suffix;
    std::string dir;
    if (KMUtil::contains(kaimingContextSpecialFilesystems, filesystem) || getXdgUserDirFromString(filesystem, configKey, suffix, dir) || KMUtil::startsWith(filesystem, "~/") || KMUtil::startsWith(filesystem, "/"))
    {
        filesystem_out = filesystem;
        return true;
    }

    if (filesystem == "~")
    {
        filesystem_out = "home";

        return true;
    }

    if (KMUtil::startsWith(filesystem, "home/"))
    {
        filesystem_out = std::string("~/") + filesystem.substr(5);

        return true;
    }

    KMError(filesystem + "is unknown filesystem location, valid locations are: host, host-os, host-etc, home, xdg-*[/…], ~/dir, /dir");
    return false;
}

void KMContext::takeFilesystem(const std::string &fs, const KMFilesystemMode &mode)
{
    /* Special case: --nofilesystem=host-reset implies --nofilesystem=host.
     * --filesystem=host-reset (or host:reset) is not allowed. */
    if (fs == "host-reset")
    {
        if (mode != KAIMING_FILESYSTEM_MODE_NONE)
        {
            return;
        }

        m_filesystems.insert({ "host", mode });
    }

    m_filesystems.insert({ fs, mode });
}

bool KMContext::addShares(const std::string &share)
{
    KMContextShares shares = (KMContextShares)bitmaskFromString(share.c_str(), kaimingContextShares);
    if (shares == KAIMING_CONTEXT_SHARED_NONE)
    {
        KMError(share + " is unknowned share type, valid types are : " + KMUtil::join(kaimingContextShares, ","));
        return false;
    }

    addShares(shares);

    return true;
}

bool KMContext::removeShares(const std::string &share)
{
    KMContextShares shares = (KMContextShares)bitmaskFromString(share.c_str(), kaimingContextShares);
    if (shares == KAIMING_CONTEXT_SHARED_NONE)
    {
        KMError(share + " is unknowned share type, valid types are : " + KMUtil::join(kaimingContextShares, ","));
        return false;
    }

    removeShares(shares);

    return true;
}

bool KMContext::addSockets(const std::string &socket)
{
    KMContextSockets sockets = (KMContextSockets)bitmaskFromString(socket.c_str(), kaimingContextSockets);
    if (sockets == KAIMING_CONTEXT_SOCKET_NONE)
    {
        KMError(socket + " is unknowned socket type, valid types are : " + KMUtil::join(kaimingContextSockets, ","));
        return false;
    }
    else if (sockets == KAIMING_CONTEXT_SOCKET_FALLBACK_X11)
    {
        sockets = (KMContextSockets)(sockets | KAIMING_CONTEXT_SOCKET_X11);
    }

    addSockets(sockets);

    return true;
}

bool KMContext::removeSockets(const std::string &socket)
{
    KMContextSockets sockets = (KMContextSockets)bitmaskFromString(socket.c_str(), kaimingContextSockets);
    if (sockets == KAIMING_CONTEXT_SOCKET_NONE)
    {
        KMError(socket + " is unknowned socket type, valid types are : " + KMUtil::join(kaimingContextSockets, ","));
        return false;
    }
    else if (sockets == KAIMING_CONTEXT_SOCKET_FALLBACK_X11)
    {
        sockets = (KMContextSockets)(sockets | KAIMING_CONTEXT_SOCKET_X11);
    }

    removeSockets(sockets);

    return true;
}

bool KMContext::addDevices(const std::string &device)
{
    KMContextDevices devices = (KMContextDevices)bitmaskFromString(device.c_str(), kaimingContextDevices);
    if (devices == KAIMING_CONTEXT_DEVICE_NONE)
    {
        KMError(device + " is unknowned device type, valid types are : " + KMUtil::join(kaimingContextDevices, ","));
        return false;
    }

    addDevices(devices);

    return true;
}

bool KMContext::removeDevices(const std::string &device)
{
    if (device == "camera")
    {
        m_devices = KAIMING_CONTEXT_DEVICE_NONE;
        return true;
    }
    KMContextDevices devices = (KMContextDevices)bitmaskFromString(device.c_str(), kaimingContextDevices);
    if (devices == KAIMING_CONTEXT_DEVICE_NONE)
    {
        KMError(device + " is unknowned device type, valid types are : " + KMUtil::join(kaimingContextDevices, ","));
        return false;
    }

    removeDevices(devices);

    return true;
}

bool KMContext::addFeatures(const std::string &feature)
{
    KMContextFeatures devices = (KMContextFeatures)bitmaskFromString(feature.c_str(), kaimingContextFeatures);
    if (devices == KAIMING_CONTEXT_FEATURE_NONE)
    {
        KMError(feature + " is unknowned feature type, valid types are : " + KMUtil::join(kaimingContextFeatures, ","));
        return false;
    }

    addFeatures(devices);

    return true;
}

bool KMContext::removeFeatures(const std::string &feature)
{
    KMContextFeatures devices = (KMContextFeatures)bitmaskFromString(feature.c_str(), kaimingContextFeatures);
    if (devices == KAIMING_CONTEXT_FEATURE_NONE)
    {
        KMError(feature + " is unknowned feature type, valid types are : " + KMUtil::join(kaimingContextFeatures, ","));
        return false;
    }

    removeFeatures(devices);

    return true;
}

void KMContext::addEnvVar(const std::string &env, const std::string &value)
{
    if (env.empty())
    {
        return;
    }

    m_envVars.insert({ env, value });
}

void KMContext::setSessionBusPolicy(const std::string &name, KMPolicy policy)
{
    m_sessionBusPolicy.insert({ name, policy });
}

void KMContext::setSystemBusPolicy(const std::string &name, KMPolicy policy)
{
    m_systemBusPolicy.insert({ name, policy });
}

bool KMContext::applyGenericPolicy(const std::string &policyKey, const std::string &value)
{
    if (!KMUtil::contains(policyKey, "."))
    {
        KMError(policyKey + " is wrong, must be in the form SUBSYSTEM.KEY");
        return false;
    }

    if (0 < m_genericPolicy.count(policyKey))
    {
        auto &list = m_genericPolicy[policyKey];
        for (std::vector<std::string>::iterator it = list.begin(); it != list.end(); ++it)
        {
            if (*it == (std::string("!") + value) || (std::string("!") + *it) == value)
            {
                list.erase(it);
                break;
            }
        }
        list.push_back(value);
    }
    else
    {
        std::vector<std::string> list;
        list.push_back(value);
        m_genericPolicy[policyKey] = list;
    }

    return true;
}

void KMContext::merge(const KMContext &other)
{
    m_shares = (KMContextShares)(m_shares & (~other.m_sharesValid));
    m_shares = (KMContextShares)(m_shares | other.m_shares);
    m_sharesValid = (KMContextShares)(m_sharesValid | other.m_sharesValid);

    m_sockets = (KMContextSockets)(m_sockets & (~other.m_socketsValid));
    m_sockets = (KMContextSockets)(m_sockets | other.m_sockets);
    m_socketsValid = (KMContextSockets)(m_socketsValid | other.m_socketsValid);

    m_devices = (KMContextDevices)(m_devices & (~other.m_devicesValid));
    m_devices = (KMContextDevices)(m_devices | other.m_devices);
    m_devicesValid = (KMContextDevices)(m_devicesValid | other.m_devicesValid);

    m_features = (KMContextFeatures)(m_features & (~other.m_featuresValid));
    m_features = (KMContextFeatures)(m_features | other.m_features);
    m_featuresValid = (KMContextFeatures)(m_featuresValid | other.m_featuresValid);

    for (auto &pair : other.m_envVars)
    {
        if (!pair.first.empty())
        {
            m_envVars.insert(pair);
        }
    }

    for (auto &pair : other.m_persistent)
    {
        if (!pair.first.empty())
        {
            m_persistent.insert(pair);
        }
    }

    /* We first handle host:reset, as it overrides all other keys from the parent */
    auto it = other.m_filesystems.find("host-reset");
    if (it != other.m_filesystems.end())
    {
        if ((*it).second == KAIMING_FILESYSTEM_MODE_NONE)
        {
            m_filesystems.clear();
        }
        else
        {
            KMError("--filesystem=host-reset, mode must be KAIMING_FILESYSTEM_MODE_NONE");
        }
    }

    /* Then set the new ones, which includes propagating host:reset. */
    // m_filesystems = other.m_filesystems;
    for (auto &pair : other.m_filesystems)
    {
        if (!pair.first.empty())
        {
            m_filesystems.insert(pair);
        }
    }

    for (auto &pair : other.m_sessionBusPolicy)
    {
        m_sessionBusPolicy.insert(pair);
    }

    for (auto &pair : other.m_systemBusPolicy)
    {
        m_systemBusPolicy.insert(pair);
    }

    for (auto &pair : other.m_genericPolicy)
    {
        auto &key = pair.first;
        auto &values = pair.second;
        for (auto &value : values)
        {
            applyGenericPolicy(key, value);
        }
    }
}

KMRunFlags KMContext::getRunFlags()
{
    KMRunFlags flags = KMRunFlags::KAIMING_RUN_FLAG_DEFAULT;

    if (allowsFeatures(KAIMING_CONTEXT_FEATURE_DEVEL))
    {
        flags = (KMRunFlags)(flags | KAIMING_RUN_FLAG_DEVEL);
    }

    if (allowsFeatures(KAIMING_CONTEXT_FEATURE_MULTIARCH))
    {
        flags = (KMRunFlags)(flags | KAIMING_RUN_FLAG_MULTIARCH);
    }

    if (allowsFeatures(KAIMING_CONTEXT_FEATURE_BLUETOOTH))
    {
        flags = (KMRunFlags)(flags | KAIMING_RUN_FLAG_BLUETOOTH);
    }

    if (allowsFeatures(KAIMING_CONTEXT_FEATURE_CANBUS))
    {
        flags = (KMRunFlags)(flags | KAIMING_RUN_FLAG_CANBUS);
    }

    return flags;
}

void KMContext::saveMetadata(bool flatten, std::shared_ptr<KMMetaData> &metadata)
{
    KMTrace("KMContext::saveMetadata invoke begin");

    KMContextShares sharesMask = m_shares;
    KMContextShares sharesValid = m_sharesValid;
    KMContextSockets socketsMask = m_sockets;
    KMContextSockets socketsValid = m_socketsValid;
    KMContextDevices devicesMask = m_devices;
    KMContextDevices devicesValid = m_devicesValid;
    KMContextFeatures featuresMask = m_features;
    KMContextFeatures featuresValid = m_featuresValid;

    if (flatten)
    {
        /* A flattened format means we don't expect this to be merged on top of
           another context. In that case we never need to negate any flags.
           We calculate this by removing the zero parts of the mask from the valid set.
         */
        /* First we make sure only the valid parts of the mask are set, in case we
           got some leftover */
        sharesMask = (KMContextShares)(sharesMask & sharesValid);
        socketsMask = (KMContextSockets)(socketsMask & socketsValid);
        devicesMask = (KMContextDevices)(devicesMask & devicesValid);
        featuresMask = (KMContextFeatures)(featuresMask & featuresValid);

        /* Then just set the valid set to be the mask set */
        sharesValid = sharesMask;
        socketsValid = socketsMask;
        devicesValid = devicesMask;
        featuresValid = featuresMask;
    }

    const std::vector<std::string> &shared = bitmaskToString(sharesMask, sharesValid, kaimingContextShares);
    const std::vector<std::string> &sockets = bitmaskToString(socketsMask, socketsValid, kaimingContextSockets);
    const std::vector<std::string> &devices = bitmaskToString(devicesMask, devicesValid, kaimingContextDevices);
    const std::vector<std::string> &features = bitmaskToString(featuresMask, featuresValid, kaimingContextFeatures);

    if (shared.empty())
    {
        metadata->deleteKey(METADATA_GROUP_CONTEXT, METADATA_KEY_SHARED);
    }
    else
    {
        metadata->setStringListValue(METADATA_GROUP_CONTEXT, METADATA_KEY_SHARED, shared);
    }

    if (sockets.empty())
    {
        metadata->deleteKey(METADATA_GROUP_CONTEXT, METADATA_KEY_SOCKETS);
    }
    else
    {
        metadata->setStringListValue(METADATA_GROUP_CONTEXT, METADATA_KEY_SOCKETS, sockets);
    }

    if (devices.empty())
    {
        metadata->deleteKey(METADATA_GROUP_CONTEXT, METADATA_KEY_DEVICES);
    }
    else
    {
        metadata->setStringListValue(METADATA_GROUP_CONTEXT, METADATA_KEY_DEVICES, devices);
    }

    if (features.empty())
    {
        metadata->deleteKey(METADATA_GROUP_CONTEXT, METADATA_KEY_FEATURES);
    }
    else
    {
        metadata->setStringListValue(METADATA_GROUP_CONTEXT, METADATA_KEY_FEATURES, features);
    }

    if (!m_filesystems.empty())
    {
        std::vector<std::string> array;
        /* Serialize host-reset first, because order can matter in corner cases. */
        if (m_filesystems.end() != m_filesystems.find("host-reset"))
        {
            if (!flatten)
            {
                array.push_back("!host:reset");
            }
        }

        for (auto &pair : m_filesystems)
        {
            KMFilesystemMode mode = pair.second;

            if (flatten && mode == KAIMING_FILESYSTEM_MODE_NONE)
            {
                continue;
            }

            /* We already did this */
            if (pair.first == "host-reset")
            {
                continue;
            }

            array.push_back(KMContext::unparseFilesystemFlags(pair.first, mode));
        }

        metadata->setStringListValue(METADATA_GROUP_CONTEXT, METADATA_KEY_FILESYSTEMS, array);
    }
    else
    {
        metadata->deleteKey(METADATA_GROUP_CONTEXT, METADATA_KEY_FILESYSTEMS);
    }

    if (!m_persistent.empty())
    {
        std::vector<std::string> array;

        for (auto &pair : m_persistent)
        {
            array.push_back(pair.first);
        }

        metadata->setStringListValue(METADATA_GROUP_CONTEXT, METADATA_KEY_PERSISTENT, array);
    }
    else
    {
        metadata->deleteKey(METADATA_GROUP_CONTEXT, METADATA_KEY_PERSISTENT);
    }

    metadata->deleteGroup(METADATA_GROUP_SESSION_BUS_POLICY);
    for (auto &pair : m_sessionBusPolicy)
    {
        KMPolicy policy = pair.second;

        if (flatten && (policy == 0))
        {
            continue;
        }

        metadata->setStringValue(METADATA_GROUP_SESSION_BUS_POLICY, pair.first, KMContext::policy2string(policy));
    }

    metadata->deleteGroup(METADATA_GROUP_SYSTEM_BUS_POLICY);
    for (auto &pair : m_systemBusPolicy)
    {
        KMPolicy policy = pair.second;

        if (flatten && (policy == 0))
        {
            continue;
        }

        metadata->setStringValue(METADATA_GROUP_SYSTEM_BUS_POLICY, pair.first, KMContext::policy2string(policy));
    }

    /* Elements are borrowed from context->env_vars */
    std::vector<std::string> unsetEnvs;
    metadata->deleteGroup(METADATA_GROUP_ENVIRONMENT);
    for (auto &pair : m_envVars)
    {
        if (pair.first.empty())
        {
            continue;
        }

        if (pair.second.empty())
        {
            unsetEnvs.push_back(pair.first);
        }
        metadata->setStringValue(METADATA_GROUP_ENVIRONMENT, pair.first, pair.second);
    }

    if (unsetEnvs.empty())
    {
        metadata->deleteKey(METADATA_GROUP_ENVIRONMENT, METADATA_KEY_UNSET_ENVIRONMENT);
    }
    else
    {
        metadata->setStringListValue(METADATA_GROUP_ENVIRONMENT, METADATA_KEY_UNSET_ENVIRONMENT, unsetEnvs);
    }

    const std::vector<std::string> &groups = metadata->getAllGroups();
    for (auto &group : groups)
    {
        if (KMUtil::startsWith(group, METADATA_GROUP_PREFIX_POLICY))
        {
            metadata->deleteGroup(group);
        }
    }

    for (auto &pair : m_genericPolicy)
    {
        std::vector<std::string> keys = KMUtil::splitString(pair.first, ".");
        assert(keys.size() == 2);
        const std::vector<std::string> &policyValues = pair.second;
        std::vector<std::string> values;

        for (auto &value : policyValues)
        {
            if (!flatten || value.at(0) != '!')
            {
                values.push_back(value);
            }
        }

        if (!values.empty())
        {
            std::string group = METADATA_GROUP_PREFIX_POLICY + keys.at(0);
            std::string key = keys.at(1);
            metadata->setStringListValue(group, key, values);
        }
    }

    KMTrace("KMContext::saveMetadata invoke end");
}

void KMContext::buildinExport(KMExports &exports, const std::string &appIdDir, const std::vector<std::string> &extraAppIdDirs, bool doCreate, std::string &xdgDirsConfOut, bool &homeAccessOut)
{
    KMTrace("KMContext::buildinExport invoke begin");

    bool homeAccess = false;
    std::string errMsg;
    std::string xdgDirsConf;

    KMFilesystemMode fsMode = KAIMING_FILESYSTEM_MODE_NONE;
    if (m_filesystems.end() != m_filesystems.find("host"))
    {
        fsMode = m_filesystems["host"];
    }
    if (fsMode != KAIMING_FILESYSTEM_MODE_NONE)
    {
        DIR *dir;
        struct dirent *dirent;

        KMDebug("Allowing host-fs access");
        homeAccess = true;

        /* Bind mount most dirs in / into the new root */
        dir = opendir("/");
        if (dir != NULL)
        {
            while ((dirent = readdir(dir)))
            {
                if (dontMountInRoot.end() != std::find(dontMountInRoot.begin(), dontMountInRoot.end(), dirent->d_name))
                {
                    continue;
                }

                std::string path = KMUtil::buildFilename("/", dirent->d_name);

                if (!exports.addPathExpose(fsMode, path, errMsg))
                {
                    /* Failure to share something like /lib32 because it's actually a symlink to /usr/lib32 is less of a problem
                     * here than it would be for an explicit --filesystem=/lib32, so the warning that would normally
                     * be produced in that situation is downgraded to a debug message. */
                    KMDebug("Failed to expose :" + errMsg);
                }
            }

            closedir(dir);
        }

        if (!exports.addPathExpose(fsMode, "/run/media", errMsg))
        {
            /* Failure to share something like /lib32 because it's actually a symlink to /usr/lib32 is less of a problem
             * here than it would be for an explicit --filesystem=/lib32, so the warning that would normally
             * be produced in that situation is downgraded to a debug message. */
            KMDebug("Failed to expose :" + errMsg);
        }
    }

    KMFilesystemMode osMode = KAIMING_FILESYSTEM_MODE_NONE;
    if (m_filesystems.end() != m_filesystems.find("host-os"))
    {
        osMode = m_filesystems["host-os"];
    }
    if (osMode != KAIMING_FILESYSTEM_MODE_NONE)
    {
        exports.addHostOsExpose(osMode);
    }

    KMFilesystemMode etcMode = KAIMING_FILESYSTEM_MODE_NONE;
    if (m_filesystems.end() != m_filesystems.find("host-etc"))
    {
        etcMode = std::max(m_filesystems["host-etc"], fsMode);
    }
    if (etcMode != KAIMING_FILESYSTEM_MODE_NONE)
    {
        exports.addHostEtcExpose(etcMode);
    }

    KMFilesystemMode home_mode = KAIMING_FILESYSTEM_MODE_NONE;
    if (m_filesystems.end() != m_filesystems.find("home"))
    {
        home_mode = std::max(m_filesystems["home"], fsMode);
    }
    if (home_mode != KAIMING_FILESYSTEM_MODE_NONE)
    {
        KMDebug("Allowing homedir access");
        homeAccess = true;

        if (!exports.addPathExpose(home_mode, g_get_home_dir(), errMsg))
        {
            /* Even if the error is one that we would normally silence, like the path not existing, it seems reasonable to make more of a fuss
             * about the home directory not existing or otherwise being unusable, so this is intentionally not using cannot_export() */
            KMWarn(_("Not allowing home directory access: ") + errMsg);
        }
    }

    for (auto &pair : m_filesystems)
    {
        const std::string &filesystem = pair.first;
        KMFilesystemMode mode = pair.second;

        if (KMUtil::contains(kaimingContextSpecialFilesystems, filesystem))
        {
            continue;
        }

        if (KMUtil::startsWith(filesystem, "xdg-"))
        {
            std::string configKey;
            std::string suffix;
            std::string dir;
            if (!getXdgUserDirFromString(filesystem, configKey, suffix, dir))
            {
                KMWarn("Unsupported xdg dir " + filesystem);
                continue;
            }

            if (dir.empty())
            {
                continue; /* Unconfigured, ignore */
            }

            if (dir == g_get_home_dir())
            {
                /* xdg-user-dirs sets disabled dirs to $HOME, and its in general not a good idea to set full access to $HOME other than explicitly, so we ignore these */
                KMWarn("Xdg dir " + filesystem + " is $HOME (i.e. disabled), ignoring");
                continue;
            }

            std::string subpath = KMUtil::buildFilename(dir, suffix);
            if (mode == KAIMING_FILESYSTEM_MODE_CREATE && doCreate)
            {
                if (KMUtil::mkpath(subpath, 0755) != 0)
                {
                    KMDebug("Unable to create directory " + subpath);
                }
            }

            if (fs::exists(subpath))
            {
                if (!configKey.empty())
                {
                    xdgDirsConf = configKey + "=" + dir;
                }

                if (!exports.addPathExposeOrHide(mode, subpath, errMsg))
                {
                    KMDebug("Unable to add expose or hide directory " + subpath);
                }
            }
        }
        else if (KMUtil::startsWith(filesystem, "~/"))
        {
            std::string path = KMUtil::buildFilename(g_get_home_dir(), filesystem.substr(2));
            if (mode == KAIMING_FILESYSTEM_MODE_CREATE && doCreate)
            {
                if (KMUtil::mkpath(path, 0755) != 0)
                {
                    KMDebug("Unable to create directory " + path);
                }
            }

            if (!exports.addPathExposeOrHide(mode, path, errMsg))
            {
                KMDebug("Unable to add expose or hide directory " + path);
            }
        }
        else if (KMUtil::startsWith(filesystem, "/"))
        {
            if (mode == KAIMING_FILESYSTEM_MODE_CREATE && doCreate)
            {
                if (KMUtil::mkpath(filesystem, 0755) != 0)
                {
                    KMDebug("Unable to create directory " + filesystem);
                }
            }

            if (!exports.addPathExposeOrHide(mode, filesystem, errMsg))
            {
                KMDebug("Unable to add expose or hide directory " + filesystem);
            }
        }
        else
        {
            KMWarn("Unexpected filesystem arg " + filesystem);
        }
    }

    if (!appIdDir.empty())
    {
        std::string appsDir = fs::path(appIdDir).parent_path().string();
        /* Hide the .var/app dir by default (unless explicitly made visible) */
        if (!exports.addPathTmpfs(appsDir, errMsg))
        {
            KMDebug("Unable to add path tmpfs " + appsDir);
        }

        /* But let the app write to the per-app dir in it */
        if (!exports.addPathExpose(KAIMING_FILESYSTEM_MODE_READ_WRITE, appIdDir, errMsg))
        {
            KMDebug("Unable to add expose directory " + appIdDir);
        }

        for (auto &dir : extraAppIdDirs)
        {
            if (!exports.addPathExpose(KAIMING_FILESYSTEM_MODE_READ_WRITE, dir, errMsg))
            {
                KMDebug("Unable to add expose directory " + dir);
            }
        }
    }

    xdgDirsConfOut = xdgDirsConf;
    homeAccessOut = homeAccess;

    KMTrace("KMContext::buildinExport invoke end");
}

std::shared_ptr<KMExports> KMContext::getExportsFull(const std::string &appIdDir, const std::vector<std::string> &extraAppIdDirs, bool doCreate, bool includeDefaultDirs, std::string &xdgDirsConfOut, bool &homeAccessOut)
{
    KMTrace("KMContext::getExportsFull invoke begin");

    std::string errMsg;

    std::shared_ptr<KMExports> exports = std::make_shared<KMExports>();
    buildinExport(*exports, appIdDir, extraAppIdDirs, doCreate, xdgDirsConfOut, homeAccessOut);

    if (includeDefaultDirs)
    {
        /* Hide the kaiming dir by default (unless explicitly made visible) */
        std::string userKaimingDir = KMStorageDir::getUserBaseDirLocation();
        if (!exports->addPathTmpfs(userKaimingDir, errMsg))
        {
            KMWarn(errMsg);
        }

        /* Ensure we always have a homedir */
        if (!exports->addPathDir(g_get_home_dir(), errMsg))
        {
            KMWarn(_("Unable to provide a temporary home directory in the sandbox: ") + errMsg);
        }
    }

    KMTrace("KMContext::getExportsFull invoke end");
    return exports;
}

std::vector<std::string> KMContext::getSessionBusPolicyAllowedOwnNames()
{
    std::vector<std::string> names;
    for (auto &pair : m_sessionBusPolicy)
    {
        if (pair.second == KAIMING_POLICY_OWN)
        {
            names.push_back(pair.first);
        }
    }

    return names;
}
