carb/launcher/LauncherUtils.h

File members: carb/launcher/LauncherUtils.h

// Copyright (c) 2020-2023, NVIDIA CORPORATION. All rights reserved.
//
// NVIDIA CORPORATION and its licensors retain all intellectual property
// and proprietary rights in and to this software, related documentation
// and any modifications thereto. Any use, reproduction, disclosure or
// distribution of this software and related documentation without an express
// license agreement from NVIDIA CORPORATION is strictly prohibited.
//
#pragma once

#include "../Defines.h"
#include "../extras/StringSafe.h"
#include "../../omni/extras/PathMap.h"
#include "../settings/SettingsUtils.h"

#include <string>
#include <unordered_map>
#include <vector>
#include <sstream>

#if CARB_PLATFORM_LINUX
#    include <unistd.h>
#elif CARB_PLATFORM_MACOS
#    include <crt_externs.h>
#else
#    include "../CarbWindows.h"
#    include "../extras/Unicode.h"
#endif

namespace carb
{
namespace launcher
{

using SettingsEnumFlags = uint32_t;

constexpr SettingsEnumFlags fSettingsEnumFlagRecursive = 0x01;

using AddSettingPredicateFn = bool (*)(const char* path, void* context);

class ArgCollector
{
public:
    ArgCollector() = default;

    ArgCollector(const ArgCollector& rhs)
    {
        *this = rhs;
    }

    ArgCollector(ArgCollector&& rhs)
    {
        *this = std::move(rhs);
    }

    ~ArgCollector()
    {
        clear();
    }

    void clear()
    {
        m_argList.reset();
        m_args.clear();
        m_allocCount = 0;
    }

    const char* const* getArgs(size_t* argCountOut = nullptr)
    {
        if (argCountOut)
        {
            *argCountOut = m_args.size();
        }

        if (m_args.empty())
        {
            return emptyArgList();
        }

        if (m_allocCount < m_args.size())
        {
            m_allocCount = m_args.size();
            m_argList.reset(new (std::nothrow) const char*[m_args.size() + 1]);
            if (CARB_UNLIKELY(m_argList == nullptr))
            {
                if (argCountOut)
                {
                    *argCountOut = 0;
                }
                m_allocCount = 0;
                return nullptr;
            }
        }

        for (size_t i = 0; i < m_args.size(); i++)
        {
            m_argList[i] = m_args[i].c_str();
        }

        // null terminate the list since some platforms and apps expect that behavior.
        m_argList[m_args.size()] = nullptr;
        return m_argList.get();
    }

    size_t getCount() const
    {
        return m_args.size();
    }

    ArgCollector& operator=(const ArgCollector& rhs)
    {
        if (this == &rhs)
            return *this;

        clear();

        if (rhs.m_args.size() == 0)
            return *this;

        m_args = rhs.m_args;
        return *this;
    }

    ArgCollector& operator=(ArgCollector&& rhs)
    {
        if (this == &rhs)
            return *this;

        clear();

        m_args = std::move(rhs.m_args);
        return *this;
    }

    bool operator==(const ArgCollector& rhs)
    {
        size_t count = m_args.size();

        if (&rhs == this)
            return true;

        if (count != rhs.m_args.size())
            return false;

        for (size_t i = 0; i < count; i++)
        {
            if (m_args[i] != rhs.m_args[i])
                return false;
        }

        return true;
    }

    bool operator!=(const ArgCollector& rhs)
    {
        return !(*this == rhs);
    }

    bool operator!() const
    {
        return m_args.size() == 0;
    }

    explicit operator bool() const
    {
        return !m_args.empty();
    }

    ArgCollector& format(const char* fmt, ...) CARB_PRINTF_FUNCTION(2, 3)
    {
        CARB_FORMATTED(fmt, [&](const char* p) { add(p); });
        return *this;
    }

    ArgCollector& add(const char* value)
    {
        m_args.push_back(value);
        return *this;
    }

    ArgCollector& add(const std::string& value)
    {
        m_args.push_back(value);
        return *this;
    }

    ArgCollector& add(const ArgCollector& value)
    {
        for (auto& arg : value.m_args)
            m_args.push_back(arg);
        return *this;
    }

    ArgCollector& add(const char* const* value)
    {
        for (const char* const* arg = value; arg[0] != nullptr; arg++)
            m_args.push_back(arg[0]);
        return *this;
    }

    ArgCollector& add(const std::vector<const char*>& value)
    {
        for (auto& v : value)
            add(v);
        return *this;
    }

    ArgCollector& add(const std::vector<std::string>& value)
    {
        for (auto& v : value)
            add(v);
        return *this;
    }

    ArgCollector& operator+=(const char* value)
    {
        return add(value);
    }

    ArgCollector& operator+=(const std::string& value)
    {
        return add(value);
    }

    ArgCollector& operator+=(const ArgCollector& value)
    {
        return add(value);
    }

    ArgCollector& operator+=(const char* const* value)
    {
        return add(value);
    }

    ArgCollector& operator+=(const std::vector<const char*>& value)
    {
        return add(value);
    }

    ArgCollector& operator+=(const std::vector<std::string>& value)
    {
        return add(value);
    }

#define ADD_PRIMITIVE_HANDLER(type, fmt)                                                                               \
                                                                                                                \
    ArgCollector& add(type value)                                                                                      \
    {                                                                                                                  \
        char buffer[128];                                                                                              \
        carb::extras::formatString(buffer, CARB_COUNTOF(buffer), fmt, value);                                          \
        m_args.push_back(buffer);                                                                                      \
        return *this;                                                                                                  \
    }                                                                                                                  \
                                                                                          \
    ArgCollector& operator+=(type value)                                                                               \
    {                                                                                                                  \
        return add(value);                                                                                             \
    }

    // unsigned integer handlers.
    ADD_PRIMITIVE_HANDLER(unsigned char, "%u")
    ADD_PRIMITIVE_HANDLER(unsigned short, "%u")
    ADD_PRIMITIVE_HANDLER(unsigned int, "%u")
    ADD_PRIMITIVE_HANDLER(unsigned long, "%lu")
    ADD_PRIMITIVE_HANDLER(unsigned long long, "%llu")

    // signed integer handlers.
    ADD_PRIMITIVE_HANDLER(char, "%d")
    ADD_PRIMITIVE_HANDLER(short, "%d")
    ADD_PRIMITIVE_HANDLER(int, "%d")
    ADD_PRIMITIVE_HANDLER(long, "%ld")
    ADD_PRIMITIVE_HANDLER(long long, "%lld")

    // other numerical handlers.  Note that some of these can be trivially implicitly cast to
    // other primitive types so we can't define them again.  Specifically the size_t,
    // intmax_t, and uintmax_t types often match other types with handlers defined above.
    // Which handler each of these matches to will differ by platform however.
    ADD_PRIMITIVE_HANDLER(float, "%.10f")
    ADD_PRIMITIVE_HANDLER(double, "%.20f")

#undef ADD_PRIMITIVE_HANDLER

    ArgCollector& add(const char* root,
                      const char* prefix,
                      SettingsEnumFlags flags = 0,
                      AddSettingPredicateFn predicate = nullptr,
                      void* context = nullptr)
    {
        dictionary::IDictionary* dictionary = getCachedInterface<dictionary::IDictionary>();
        settings::ISettings* settings = getCachedInterface<settings::ISettings>();
        std::string rootPath;

        auto addSetting = [&](const char* path, int32_t elementData, void* context) -> int32_t {
            dictionary::ItemType type;
            CARB_UNUSED(context);

            type = settings->getItemType(path);

            // skip dictionaries since we're only interested in leaves here.
            if (type == dictionary::ItemType::eDictionary)
                return elementData + 1;

            if ((flags & fSettingsEnumFlagRecursive) == 0 && elementData > 1)
                return elementData;

            // verify that the caller wants this setting added.
            if (predicate != nullptr && !predicate(path, context))
                return elementData + 1;

            switch (type)
            {
                case dictionary::ItemType::eBool:
                    format("%s%s=%s", prefix, &path[1], settings->getAsBool(path) ? "true" : "false");
                    break;

                case dictionary::ItemType::eInt:
                    format("%s%s=%" PRId64, prefix, &path[1], settings->getAsInt64(path));
                    break;

                case dictionary::ItemType::eFloat:
                    format("%s%s=%g", prefix, &path[1], settings->getAsFloat64(path));
                    break;

                case dictionary::ItemType::eString:
                    format("%s%s=\"%s\"", prefix, &path[1], settings->getStringBuffer(path));
                    break;

                default:
                    break;
            }

            return elementData;
        };

        // unexpected root prefix (not an absolute settings path) => fail.
        if (root == nullptr || root[0] == 0)
            root = "/";

        // avoid a `nullptr` check later.
        if (prefix == nullptr)
            prefix = "";

        // make sure to strip off any trailing separators since that would break the lookups.
        rootPath = root;

        if (rootPath.size() > 1 && rootPath[rootPath.size() - 1] == '/')
            rootPath = rootPath.substr(0, rootPath.size() - 1);

        // walk the settings tree to collect all the requested settings.
        settings::walkSettings(
            dictionary, settings, dictionary::WalkerMode::eIncludeRoot, rootPath.c_str(), 0, addSetting, context);
        return *this;
    }

    const std::string& at(size_t index) const
    {
        if (index >= m_args.size())
            return m_empty;

        return m_args[index];
    }

    const std::string& operator[](size_t index) const
    {
        return at(index);
    }

    void pop()
    {
        if (m_args.empty())
            return;

        m_args.pop_back();
    }

    bool erase(size_t index)
    {
        if (index >= m_args.size())
            return false;

        m_args.erase(m_args.begin() + index);
        return true;
    }

    std::string toString() const
    {
        std::ostringstream stream;
        for (auto& arg : m_args)
        {
            size_t index = size_t(-1);
            for (;;)
            {
                size_t start = index + 1;
                index = arg.find_first_of("\\\" '", start); // Characters that must be escaped
                stream << arg.substr(start, index - start);
                if (index == std::string::npos)
                    break;
                stream << '\\' << arg[index];
            }

            // Always add a trailing space. It will be popped before return.
            stream << ' ';
        }

        std::string out = stream.str();
        if (!out.empty())
            out.pop_back(); // Remove the extra space

        return out;
    }

private:
    static const char* const* emptyArgList()
    {
        static const char* const empty{ nullptr };
        return &empty;
    }

    std::string m_empty;

    std::vector<std::string> m_args;

    std::unique_ptr<const char*[]> m_argList;

    size_t m_allocCount = 0;
};

class EnvCollector
{
public:
    EnvCollector() = default;

    EnvCollector(const EnvCollector& rhs)
    {
        *this = rhs;
    }

    EnvCollector(EnvCollector&& rhs)
    {
        *this = std::move(rhs);
    }

    ~EnvCollector()
    {
        clear();
    }

    void clear()
    {
        m_env.clear();
        m_args.clear();
    }

    const char* const* getEnv()
    {
        m_args.clear();

        for (auto& var : m_env)
            m_args.format("%s=%s", var.first.c_str(), var.second.c_str());

        return m_args.getArgs();
    }

    size_t getCount() const
    {
        return m_env.size();
    }

    EnvCollector& operator=(const EnvCollector& rhs)
    {
        if (this == &rhs)
            return *this;

        clear();

        if (rhs.m_env.size() == 0)
            return *this;

        m_env = rhs.m_env;
        return *this;
    }

    EnvCollector& operator=(EnvCollector&& rhs)
    {
        if (this == &rhs)
            return *this;

        clear();

        m_env = std::move(rhs.m_env);
        return *this;
    }

    bool operator==(const EnvCollector& rhs)
    {
        size_t count = m_env.size();

        if (&rhs == this)
            return true;

        if (count != rhs.m_env.size())
            return false;

        for (auto var : m_env)
        {
            auto other = rhs.m_env.find(var.first);

            if (other == rhs.m_env.end())
                return false;

            if (other->second != var.second)
                return false;
        }

        return true;
    }

    bool operator!=(const EnvCollector& rhs)
    {
        return !(*this == rhs);
    }

    bool operator!()
    {
        return m_env.size() == 0;
    }

    operator bool()
    {
        return !m_env.empty();
    }

    EnvCollector& add(const char* name, const char* value)
    {
        m_env[name] = value == nullptr ? "" : value;
        return *this;
    }

    EnvCollector& add(const std::string& name, const std::string& value)
    {
        m_env[name] = value;
        return *this;
    }

    EnvCollector& add(const std::string& name, const char* value)
    {
        m_env[name] = value == nullptr ? "" : value;
        return *this;
    }

    EnvCollector& add(const char* name, const std::string& value)
    {
        m_env[name] = value;
        return *this;
    }

#define ADD_PRIMITIVE_HANDLER(type, fmt)                                                                               \
                                                                                                                \
    EnvCollector& add(const char* name, type value)                                                                    \
    {                                                                                                                  \
        char buffer[128];                                                                                              \
        carb::extras::formatString(buffer, CARB_COUNTOF(buffer), fmt, value);                                          \
        return add(name, buffer);                                                                                      \
    }                                                                                                                  \
                                                                              \
    EnvCollector& add(const std::string& name, type value)                                                             \
    {                                                                                                                  \
        return add(name.c_str(), value);                                                                               \
    }

    // unsigned integer handlers.
    ADD_PRIMITIVE_HANDLER(uint8_t, "%" PRIu8)
    ADD_PRIMITIVE_HANDLER(uint16_t, "%" PRIu16)
    ADD_PRIMITIVE_HANDLER(uint32_t, "%" PRIu32)
    ADD_PRIMITIVE_HANDLER(uint64_t, "%" PRIu64)

    // signed integer handlers.
    ADD_PRIMITIVE_HANDLER(int8_t, "%" PRId8)
    ADD_PRIMITIVE_HANDLER(int16_t, "%" PRId16)
    ADD_PRIMITIVE_HANDLER(int32_t, "%" PRId32)
    ADD_PRIMITIVE_HANDLER(int64_t, "%" PRId64)

    // other numerical handlers.  Note that some of these can be trivially implicitly cast to
    // other primitive types so we can't define them again.  Specifically the size_t,
    // intmax_t, and uintmax_t types often match other types with handlers defined above.
    // Which handler each of these matches to will differ by platform however.
    ADD_PRIMITIVE_HANDLER(float, "%.10f")
    ADD_PRIMITIVE_HANDLER(double, "%.20f")

#undef ADD_PRIMITIVE_HANDLER

    EnvCollector& add(const char* var)
    {
        const char* sep;

#if CARB_PLATFORM_WINDOWS
        // Windows' environment sets variables such as "=C:=C:\".  We need to handle this case.
        // Here the variable's name is "=C:" and its value is "C:\".  This similar behavior is
        // not allowed on Linux however.
        if (var[0] == '=')
            sep = strchr(var + 1, '=');

        else
#endif
            sep = strchr(var, '=');

        // no assignment in the string => clear out that variable.
        if (sep == nullptr)
        {
            m_env[var] = "";
            return *this;
        }

        m_env[std::string(var, sep - var)] = sep + 1;
        return *this;
    }

    EnvCollector& add(const std::string& var)
    {
        return add(var.c_str());
    }

    EnvCollector& operator+=(const char* var)
    {
        return add(var);
    }

    EnvCollector& operator+=(const std::string& var)
    {
        return add(var.c_str());
    }

    EnvCollector& add(const char* const* vars)
    {
        for (const char* const* var = vars; var[0] != nullptr; var++)
            add(var[0]);
        return *this;
    }

    EnvCollector& add(const std::vector<const char*>& vars)
    {
        for (auto& v : vars)
            add(v);
        return *this;
    }

    EnvCollector& add(const std::vector<std::string>& vars)
    {
        for (auto& v : vars)
            add(v);
        return *this;
    }

    EnvCollector& add(const EnvCollector& vars)
    {
        for (auto& var : vars.m_env)
            m_env[var.first] = var.second;
        return *this;
    }

    EnvCollector& operator+=(const char* const* vars)
    {
        return add(vars);
    }

    EnvCollector& operator+=(const std::vector<const char*>& vars)
    {
        return add(vars);
    }

    EnvCollector& operator+=(const std::vector<std::string>& vars)
    {
        return add(vars);
    }

    EnvCollector& operator+=(const EnvCollector& vars)
    {
        return add(vars);
    }

#if CARB_POSIX
    EnvCollector& add()
    {
#    if CARB_PLATFORM_MACOS
        char*** tmp = _NSGetEnviron(); // see man 7 environ
        if (tmp == nullptr)
        {
            CARB_LOG_ERROR("_NSGetEnviron() returned nullptr");
            return *this;
        }

        char** environ = *tmp;
#    endif

        for (char** env = environ; env[0] != nullptr; env++)
            add(env[0]);
        return *this;
    }
#else
    EnvCollector& add()
    {
        LPWCH origEnv = GetEnvironmentStringsW();
        LPWCH env = origEnv;
        std::string var;
        size_t len;

        // walk the environment strings table and add each variable to this object.
        for (len = wcslen(env); env[0] != 0; env += len + 1, len = wcslen(env))
        {
            var = extras::convertWideToUtf8(env);
            add(var);
        }

        FreeEnvironmentStringsW(origEnv);
        return *this;
    }
#endif

    EnvCollector& remove(const char* name)
    {
        m_env.erase(name);
        return *this;
    }

    EnvCollector& remove(const std::string& name)
    {
        return remove(name.c_str());
    }

    EnvCollector& operator-=(const char* name)
    {
        return remove(name);
    }

    EnvCollector& operator-=(const std::string& name)
    {
        return remove(name.c_str());
    }

    const std::string& at(const char* name)
    {
        auto var = m_env.find(name);

        if (var == m_env.end())
            return m_empty;

        return var->second;
    }

    const std::string& at(const std::string& name)
    {
        return at(name.c_str());
    }

    const std::string& operator[](const char* name)
    {
        return at(name);
    }

    const std::string& operator[](const std::string& name)
    {
        return at(name.c_str());
    }

private:
    omni::extras::UnorderedPathMap<std::string> m_env;

    ArgCollector m_args;

    std::string m_empty;
};

} // namespace launcher
} // namespace carb