/* $Id: dbgcfg.cpp $ */
/** @file
 * IPRT - Debugging Configuration.
 */

/*
 * Copyright (C) 2013 Oracle Corporation
 *
 * This file is part of VirtualBox Open Source Edition (OSE), as
 * available from http://www.virtualbox.org. This file is free software;
 * you can redistribute it and/or modify it under the terms of the GNU
 * General Public License (GPL) as published by the Free Software
 * Foundation, in version 2 as it comes in the "COPYING" file of the
 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
 *
 * The contents of this file may alternatively be used under the terms
 * of the Common Development and Distribution License Version 1.0
 * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
 * VirtualBox OSE distribution, in which case the provisions of the
 * CDDL are applicable instead of those of the GPL.
 *
 * You may elect to license modified versions of this file under the
 * terms and conditions of either the GPL or the CDDL or both.
 */


/*******************************************************************************
*   Header Files                                                               *
*******************************************************************************/
#define LOG_GROUP RTLOGGROUP_DBG
#include <iprt/dbg.h>
#include "internal/iprt.h"

#include <iprt/alloca.h>
#include <iprt/asm.h>
#include <iprt/assert.h>
#include <iprt/critsect.h>
#include <iprt/ctype.h>
#include <iprt/dir.h>
#include <iprt/err.h>
#include <iprt/env.h>
#include <iprt/file.h>
#ifdef  IPRT_WITH_HTTP
# include <iprt/http.h>
#endif
#include <iprt/list.h>
#include <iprt/log.h>
#include <iprt/mem.h>
#include <iprt/path.h>
#include <iprt/process.h>
#include <iprt/semaphore.h>
#include <iprt/string.h>
#include <iprt/uuid.h>
#include "internal/magics.h"


/*******************************************************************************
*   Structures and Typedefs                                                    *
*******************************************************************************/
/**
 * String list entry.
 */
typedef struct RTDBGCFGSTR
{
    /** List entry. */
    RTLISTNODE  ListEntry;
    /** Domain specific flags. */
    uint16_t    fFlags;
    /** The length of the string. */
    uint16_t    cch;
    /** The string. */
    char        sz[1];
} RTDBGCFGSTR;
/** Pointer to a string list entry. */
typedef RTDBGCFGSTR *PRTDBGCFGSTR;


/**
 * Configuration instance.
 */
typedef struct RTDBGCFGINT
{
    /** The magic value (RTDBGCFG_MAGIC). */
    uint32_t            u32Magic;
    /** Reference counter. */
    uint32_t volatile   cRefs;
    /** Flags, see RTDBGCFG_FLAGS_XXX. */
    uint64_t            fFlags;

    /** List of paths to search for debug files and executable images. */
    RTLISTANCHOR        PathList;
    /** List of debug file suffixes. */
    RTLISTANCHOR        SuffixList;
    /** List of paths to search for source files. */
    RTLISTANCHOR        SrcPathList;

#ifdef RT_OS_WINDOWS
    /** The _NT_ALT_SYMBOL_PATH and _NT_SYMBOL_PATH combined. */
    RTLISTANCHOR        NtSymbolPathList;
    /** The _NT_EXECUTABLE_PATH. */
    RTLISTANCHOR        NtExecutablePathList;
    /** The _NT_SOURCE_PATH. */
    RTLISTANCHOR        NtSourcePath;
#endif

    /** Log callback function. */
    PFNRTDBGCFGLOG      pfnLogCallback;
    /** User argument to pass to the log callback. */
    void               *pvLogUser;

    /** Critical section protecting the instance data. */
    RTCRITSECTRW        CritSect;
} *PRTDBGCFGINT;

/**
 * Mnemonics map entry for a 64-bit unsigned property value.
 */
typedef struct RTDBGCFGU64MNEMONIC
{
    /** The flags to set or clear. */
    uint64_t    fFlags;
    /** The mnemonic. */
    const char *pszMnemonic;
    /** The length of the mnemonic. */
    uint8_t     cchMnemonic;
    /** If @c true, the bits in fFlags will be set, if @c false they will be
     *  cleared. */
    bool        fSet;
} RTDBGCFGU64MNEMONIC;
/** Pointer to a read only mnemonic map entry for a uint64_t property. */
typedef RTDBGCFGU64MNEMONIC const *PCRTDBGCFGU64MNEMONIC;


/** @name Open flags.
 * @{ */
/** The operative system mask.  The values are RT_OPSYS_XXX. */
#define RTDBGCFG_O_OPSYS_MASK           UINT32_C(0x000000ff)
/** The files may be compressed MS styled. */
#define RTDBGCFG_O_MAYBE_COMPRESSED_MS  RT_BIT_32(26)
/** Whether to make a recursive search. */
#define RTDBGCFG_O_RECURSIVE            RT_BIT_32(27)
/** We're looking for a separate debug file. */
#define RTDBGCFG_O_EXT_DEBUG_FILE       RT_BIT_32(28)
/** We're looking for an executable image. */
#define RTDBGCFG_O_EXECUTABLE_IMAGE     RT_BIT_32(29)
/** The file search should be done in an case insensitive fashion. */
#define RTDBGCFG_O_CASE_INSENSITIVE     RT_BIT_32(30)
/** Use Windbg style symbol servers when encountered in the path. */
#define RTDBGCFG_O_SYMSRV               RT_BIT_32(31)
/** @} */


/*******************************************************************************
*   Defined Constants And Macros                                               *
*******************************************************************************/
/** Validates a debug module handle and returns rc if not valid. */
#define RTDBGCFG_VALID_RETURN_RC(pThis, rc) \
    do { \
        AssertPtrReturn((pThis), (rc)); \
        AssertReturn((pThis)->u32Magic == RTDBGCFG_MAGIC, (rc)); \
        AssertReturn((pThis)->cRefs > 0, (rc)); \
    } while (0)


/*******************************************************************************
*   Global Variables                                                           *
*******************************************************************************/
/** Mnemonics map for RTDBGCFGPROP_FLAGS. */
static const RTDBGCFGU64MNEMONIC g_aDbgCfgFlags[] =
{
    {   RTDBGCFG_FLAGS_DEFERRED,                RT_STR_TUPLE("deferred"),       true  },
    {   RTDBGCFG_FLAGS_DEFERRED,                RT_STR_TUPLE("nodeferred"),     false },
    {   RTDBGCFG_FLAGS_NO_SYM_SRV,              RT_STR_TUPLE("symsrv"),         false },
    {   RTDBGCFG_FLAGS_NO_SYM_SRV,              RT_STR_TUPLE("nosymsrv"),       true  },
    {   RTDBGCFG_FLAGS_NO_SYSTEM_PATHS,         RT_STR_TUPLE("syspaths"),       false },
    {   RTDBGCFG_FLAGS_NO_SYSTEM_PATHS,         RT_STR_TUPLE("nosyspaths"),     true  },
    {   RTDBGCFG_FLAGS_NO_RECURSIV_SEARCH,      RT_STR_TUPLE("rec"),            false },
    {   RTDBGCFG_FLAGS_NO_RECURSIV_SEARCH,      RT_STR_TUPLE("norec"),          true  },
    {   RTDBGCFG_FLAGS_NO_RECURSIV_SRC_SEARCH,  RT_STR_TUPLE("recsrc"),         false },
    {   RTDBGCFG_FLAGS_NO_RECURSIV_SRC_SEARCH,  RT_STR_TUPLE("norecsrc"),       true  },
    {   0,                                      NULL, 0,                        false }
};



/**
 * Runtime logging, level 1.
 *
 * @param   pThis               The debug config instance data.
 * @param   pszFormat           The message format string.
 * @param   ...                 Arguments references in the format string.
 */
static void rtDbgCfgLog1(PRTDBGCFGINT pThis, const char *pszFormat, ...)
{
    if (LogIsEnabled() || (pThis && pThis->pfnLogCallback))
    {
        va_list va;
        va_start(va, pszFormat);
        char *pszMsg = RTStrAPrintf2V(pszFormat, va);
        va_end(va);

        Log(("RTDbgCfg: %s", pszMsg));
        if (pThis && pThis->pfnLogCallback)
            pThis->pfnLogCallback(pThis, 1, pszMsg, pThis->pvLogUser);
        RTStrFree(pszMsg);
    }
}


/**
 * Runtime logging, level 2.
 *
 * @param   pThis               The debug config instance data.
 * @param   pszFormat           The message format string.
 * @param   ...                 Arguments references in the format string.
 */
static void rtDbgCfgLog2(PRTDBGCFGINT pThis, const char *pszFormat, ...)
{
    if (LogIs2Enabled() || (pThis && pThis->pfnLogCallback))
    {
        va_list va;
        va_start(va, pszFormat);
        char *pszMsg = RTStrAPrintf2V(pszFormat, va);
        va_end(va);

        Log(("RTDbgCfg: %s", pszMsg));
        if (pThis && pThis->pfnLogCallback)
            pThis->pfnLogCallback(pThis, 2, pszMsg, pThis->pvLogUser);
        RTStrFree(pszMsg);
    }
}


/**
 * Checks if the file system at the given path is case insensitive or not.
 *
 * @returns true / false
 * @param   pszPath             The path to query about.
 */
static int rtDbgCfgIsFsCaseInsensitive(const char *pszPath)
{
    RTFSPROPERTIES Props;
    int rc = RTFsQueryProperties(pszPath, &Props);
    if (RT_FAILURE(rc))
        return RT_OPSYS == RT_OPSYS_DARWIN
            || RT_OPSYS == RT_OPSYS_DOS
            || RT_OPSYS == RT_OPSYS_OS2
            || RT_OPSYS == RT_OPSYS_NT
            || RT_OPSYS == RT_OPSYS_WINDOWS;
    return !Props.fCaseSensitive;
}


/**
 * Worker that does case sensitive file/dir searching.
 *
 * @returns true / false.
 * @param   pszPath         The path buffer containing an existing directory.
 *                          RTPATH_MAX in size.  On success, this will contain
 *                          the combined path with @a pszName case correct.
 * @param   offLastComp     The offset of the last component (for chopping it
 *                          off).
 * @param   pszName         What we're looking for.
 * @param   enmType         What kind of thing we're looking for.
 */
static bool rtDbgCfgIsXxxxAndFixCaseWorker(char *pszPath, size_t offLastComp, const char *pszName,
                                           RTDIRENTRYTYPE enmType)
{
    /** @todo IPRT should generalize this so we can use host specific tricks to
     *        speed it up. */

    /* Return straight away if the name isn't case foldable. */
    if (!RTStrIsCaseFoldable(pszName))
        return false;

    /*
     * Try some simple case folding games.
     */
    RTStrToLower(&pszPath[offLastComp]);
    if (RTFileExists(pszPath))
        return true;

    RTStrToUpper(&pszPath[offLastComp]);
    if (RTFileExists(pszPath))
        return true;

    /*
     * Open the directory and check each entry in it.
     */
    pszPath[offLastComp] = '\0';
    PRTDIR pDir;
    int rc = RTDirOpen(&pDir, pszPath);
    if (RT_FAILURE(rc))
        return false;

    for (;;)
    {
        /* Read the next entry. */
        union
        {
            RTDIRENTRY  Entry;
            uint8_t     ab[_4K];
        } u;
        size_t cbBuf = sizeof(u);
        rc = RTDirRead(pDir, &u.Entry, &cbBuf);
        if (RT_FAILURE(rc))
            break;

        if (   !RTStrICmp(pszName, u.Entry.szName)
            && (   u.Entry.enmType == enmType
                || u.Entry.enmType == RTDIRENTRYTYPE_UNKNOWN
                || u.Entry.enmType == RTDIRENTRYTYPE_SYMLINK) )
        {
            pszPath[offLastComp] = '\0';
            rc = RTPathAppend(pszPath, RTPATH_MAX, u.Entry.szName);
            if (   u.Entry.enmType != enmType
                && RT_SUCCESS(rc))
                RTDirQueryUnknownType(pszPath, true /*fFollowSymlinks*/, &u.Entry.enmType);

            if (   u.Entry.enmType == enmType
                || RT_FAILURE(rc))
            {
                RTDirClose(pDir);
                if (RT_FAILURE(rc))
                {
                    pszPath[offLastComp] = '\0';
                    return false;
                }
                return true;
            }
        }
    }

    RTDirClose(pDir);
    pszPath[offLastComp] = '\0';

    return false;
}


/**
 * Appends @a pszSubDir to @a pszPath and check whether it exists and is a
 * directory.
 *
 * If @a fCaseInsensitive is set, we will do a case insensitive search for a
 * matching sub directory.
 *
 * @returns true / false
 * @param   pszPath             The path buffer containing an existing
 *                              directory.  RTPATH_MAX in size.
 * @param   pszSubDir           The sub directory to append.
 * @param   fCaseInsensitive    Whether case insensitive searching is required.
 */
static bool rtDbgCfgIsDirAndFixCase(char *pszPath, const char *pszSubDir, bool fCaseInsensitive)
{
    /* Save the length of the input path so we can restore it in the case
       insensitive branch further down. */
    size_t const cchPath = strlen(pszPath);

    /*
     * Append the sub directory and check if we got a hit.
     */
    int rc = RTPathAppend(pszPath, RTPATH_MAX, pszSubDir);
    if (RT_FAILURE(rc))
        return false;

    if (RTDirExists(pszPath))
        return true;

    /*
     * Do case insensitive lookup if requested.
     */
    if (fCaseInsensitive)
        return rtDbgCfgIsXxxxAndFixCaseWorker(pszPath, cchPath, pszSubDir, RTDIRENTRYTYPE_DIRECTORY);

    pszPath[cchPath] = '\0';
    return false;
}


/**
 * Appends @a pszFilename to @a pszPath and check whether it exists and is a
 * directory.
 *
 * If @a fCaseInsensitive is set, we will do a case insensitive search for a
 * matching filename.
 *
 * @returns true / false
 * @param   pszPath             The path buffer containing an existing
 *                              directory.  RTPATH_MAX in size.
 * @param   pszFilename         The file name to append.
 * @param   fCaseInsensitive    Whether case insensitive searching is required.
 * @param   fMsCompressed       Whether to look for the MS compressed file name
 *                              variant.
 * @param   pfProbablyCompressed    This is set to true if a MS compressed
 *                                  filename variant is returned.  Optional.
 */
static bool rtDbgCfgIsFileAndFixCase(char *pszPath, const char *pszFilename, bool fCaseInsensitive,
                                     bool fMsCompressed, bool *pfProbablyCompressed)
{
    /* Save the length of the input path so we can restore it in the case
       insensitive branch further down. */
    size_t cchPath = strlen(pszPath);
    if (pfProbablyCompressed)
        *pfProbablyCompressed = false;

    /*
     * Append the filename and check if we got a hit.
     */
    int rc = RTPathAppend(pszPath, RTPATH_MAX, pszFilename);
    if (RT_FAILURE(rc))
        return false;

    if (RTFileExists(pszPath))
        return true;

    /*
     * Do case insensitive file lookup if requested.
     */
    if (fCaseInsensitive)
    {
        if (rtDbgCfgIsXxxxAndFixCaseWorker(pszPath, cchPath, pszFilename, RTDIRENTRYTYPE_FILE))
            return true;
    }

    /*
     * Look for MS compressed file if requested.
     */
    if (   fMsCompressed
        && (unsigned char)pszFilename[strlen(pszFilename) - 1] < 0x7f)
    {
        pszPath[cchPath] = '\0';
        rc = RTPathAppend(pszPath, RTPATH_MAX, pszFilename);
        AssertRCReturn(rc, false);
        pszPath[strlen(pszPath) - 1] = '_';

        if (pfProbablyCompressed)
            *pfProbablyCompressed = true;

        if (RTFileExists(pszPath))
            return true;

        if (fCaseInsensitive)
        {
            /* Note! Ugly hack here, the pszName parameter points into pszPath! */
            if (rtDbgCfgIsXxxxAndFixCaseWorker(pszPath, cchPath, RTPathFilename(pszPath), RTDIRENTRYTYPE_FILE))
                return true;
        }

        if (pfProbablyCompressed)
            *pfProbablyCompressed = false;
    }

    pszPath[cchPath] = '\0';
    return false;
}


static int rtDbgCfgTryOpenDir(PRTDBGCFGINT pThis, char *pszPath, PRTPATHSPLIT pSplitFn, uint32_t fFlags,
                              PFNDBGCFGOPEN pfnCallback, void *pvUser1, void *pvUser2)
{
    int rcRet = VWRN_NOT_FOUND;
    int rc2;

    /* If the directory doesn't exist, just quit immediately.
       Note! Our case insensitivity doesn't extend to the search dirs themselfs,
             only to the bits under neath them. */
    if (!RTDirExists(pszPath))
    {
        rtDbgCfgLog2(pThis, "Dir does not exist: '%s'\n", pszPath);
        return rcRet;
    }

    /* Figure out whether we have to do a case sensitive search or not.
       Note! As a simplification, we don't ask for case settings in each
             directory under the user specified path, we assume the file
             systems that mounted there have compatible settings. Faster
             that way. */
    bool const fCaseInsensitive = (fFlags & RTDBGCFG_O_CASE_INSENSITIVE)
                               && !rtDbgCfgIsFsCaseInsensitive(pszPath);

    size_t const cchPath = strlen(pszPath);

    /*
     * Look for the file with less and less of the original path given.
     */
    for (unsigned i = RTPATH_PROP_HAS_ROOT_SPEC(pSplitFn->fProps); i < pSplitFn->cComps; i++)
    {
        pszPath[cchPath] = '\0';

        rc2 = VINF_SUCCESS;
        for (unsigned j = i; j < pSplitFn->cComps - 1U && RT_SUCCESS(rc2); j++)
            if (!rtDbgCfgIsDirAndFixCase(pszPath, pSplitFn->apszComps[i], fCaseInsensitive))
                rc2 = VERR_FILE_NOT_FOUND;

        if (RT_SUCCESS(rc2))
        {
            if (rtDbgCfgIsFileAndFixCase(pszPath, pSplitFn->apszComps[pSplitFn->cComps - 1], fCaseInsensitive, false, NULL))
            {
                rtDbgCfgLog1(pThis, "Trying '%s'...\n", pszPath);
                rc2 = pfnCallback(pThis, pszPath, pvUser1, pvUser2);
                if (rc2 == VINF_CALLBACK_RETURN || rc2 == VERR_CALLBACK_RETURN)
                {
                    if (rc2 == VINF_CALLBACK_RETURN)
                        rtDbgCfgLog1(pThis, "Found '%s'.\n", pszPath);
                    else
                        rtDbgCfgLog1(pThis, "Error opening '%s'.\n", pszPath);
                    return rc2;
                }
                rtDbgCfgLog1(pThis, "Error %Rrc opening '%s'.\n", rc2, pszPath);
                if (RT_FAILURE(rc2) && RT_SUCCESS_NP(rcRet))
                    rcRet = rc2;
            }
        }
    }

    /*
     * Do a recursive search if requested.
     */
    if (   (fFlags & RTDBGCFG_O_RECURSIVE)
        && !(pThis->fFlags & RTDBGCFG_FLAGS_NO_RECURSIV_SEARCH) )
    {
        /** @todo Recursive searching will be done later. */
    }

    return rcRet;
}

static int rtDbgCfgUnpackMsCacheFile(PRTDBGCFGINT pThis, char *pszPath, const char *pszFilename)
{
    rtDbgCfgLog2(pThis, "Unpacking '%s'...\n", pszPath);

    /*
     * Duplicate the source file path, just for simplicity and restore the
     * final character in the orignal.  We cheerfully ignorining any
     * possibility of multibyte UTF-8 sequences just like the caller did when
     * setting it to '_'.
     */
    char *pszSrcArchive = RTStrDup(pszPath);
    if (!pszSrcArchive)
        return VERR_NO_STR_MEMORY;

    pszPath[strlen(pszPath) - 1] = RT_C_TO_LOWER(pszFilename[strlen(pszFilename) - 1]);


    /*
     * Figuring out the argument list for the platform specific unpack util.
     */
#ifdef RT_OS_WINDOWS
    const char *papszArgs[] =
    {
        "expand.exe",
        pszSrcArchive,
        pszPath,
        NULL
    };

#else
    char szExtractDir[RTPATH_MAX];
    strcpy(szExtractDir, pszPath);
    RTPathStripFilename(szExtractDir);

    const char *papszArgs[] =
    {
        "cabextract",
        "-L",                   /* Lower case extracted files. */
        "-d", szExtractDir,     /* Extraction path */
        pszSrcArchive,
        NULL
    };
#endif

    /*
     * Do the unpacking.
     */
    RTPROCESS hChild;
    int rc = RTProcCreate(papszArgs[0], papszArgs, RTENV_DEFAULT,
#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
                          RTPROC_FLAGS_NO_WINDOW | RTPROC_FLAGS_HIDDEN | RTPROC_FLAGS_SEARCH_PATH,
#else
                          RTPROC_FLAGS_SEARCH_PATH,
#endif
                          &hChild);
    if (RT_SUCCESS(rc))
    {
        RTPROCSTATUS ProcStatus;
        rc = RTProcWait(hChild, RTPROCWAIT_FLAGS_BLOCK, &ProcStatus);
        if (RT_SUCCESS(rc))
        {
            if (   ProcStatus.enmReason == RTPROCEXITREASON_NORMAL
                && ProcStatus.iStatus   == 0)
            {
                if (RTPathExists(pszPath))
                {
                    rtDbgCfgLog1(pThis, "Successfully unpacked '%s' to '%s'.\n", pszSrcArchive, pszPath);
                    rc = VINF_SUCCESS;
                }
                else
                {
                    rtDbgCfgLog1(pThis, "Successfully ran unpacker on '%s', but '%s' is missing!\n", pszSrcArchive, pszPath);
                    rc = VERR_ZIP_ERROR;
                }
            }
            else
            {
                rtDbgCfgLog2(pThis, "Unpacking '%s' failed: iStatus=%d enmReason=%d\n",
                             pszSrcArchive, ProcStatus.iStatus, ProcStatus.enmReason);
                rc = VERR_ZIP_CORRUPTED;
            }
        }
        else
            rtDbgCfgLog1(pThis, "Error waiting for process: %Rrc\n", rc);

    }
    else
        rtDbgCfgLog1(pThis, "Error starting unpack process '%s': %Rrc\n", papszArgs[0], rc);

    return rc;
}

static int rtDbgCfgTryDownloadAndOpen(PRTDBGCFGINT pThis, const char *pszServer,
                                      char *pszPath, const char *pszCacheSubDir, PRTPATHSPLIT pSplitFn,
                                      uint32_t fFlags, PFNDBGCFGOPEN pfnCallback, void *pvUser1, void *pvUser2)
{
#ifdef IPRT_WITH_HTTP
    if (pThis->fFlags & RTDBGCFG_FLAGS_NO_SYM_SRV)
        return VWRN_NOT_FOUND;
    if (!pszCacheSubDir || !*pszCacheSubDir)
        return VWRN_NOT_FOUND;

    /*
     * Create the path.
     */
    size_t cchTmp = strlen(pszPath);

    int rc = RTDirCreateFullPath(pszPath, 0766);
    if (!RTDirExists(pszPath))
    {
        Log(("Error creating cache dir '%s': %Rrc\n", pszPath, rc));
        return rc;
    }

    const char *pszFilename = pSplitFn->apszComps[pSplitFn->cComps - 1];
    rc = RTPathAppend(pszPath, RTPATH_MAX, pszFilename);
    if (RT_FAILURE(rc))
        return rc;
    RTStrToLower(&pszPath[cchTmp]);
    if (!RTDirExists(pszPath))
    {
        rc = RTDirCreate(pszPath, 0766, 0);
        if (RT_FAILURE(rc))
        {
            Log(("RTDirCreate(%s) -> %Rrc\n", pszPath, rc));
        }
    }

    rc = RTPathAppend(pszPath, RTPATH_MAX, pszCacheSubDir);
    if (RT_FAILURE(rc))
        return rc;
    if (!RTDirExists(pszPath))
    {
        rc = RTDirCreate(pszPath, 0766, 0);
        if (RT_FAILURE(rc))
        {
            Log(("RTDirCreate(%s) -> %Rrc\n", pszPath, rc));
        }
    }

    /* Prepare the destination file name while we're here. */
    cchTmp = strlen(pszPath);
    RTStrToLower(&pszPath[cchTmp]);
    rc = RTPathAppend(pszPath, RTPATH_MAX, pszFilename);
    if (RT_FAILURE(rc))
        return rc;

    /*
     * Download the file.
     */
    RTHTTP hHttp;
    rc = RTHttpCreate(&hHttp);
    if (RT_FAILURE(rc))
        return rc;
    RTHttpUseSystemProxySettings(hHttp);

    static const char * const s_apszHeaders[] =
    {
        "User-Agent: Microsoft-Symbol-Server/6.6.0999.9",
        "Pragma: no-cache",
    };

    rc = RTHttpSetHeaders(hHttp, RT_ELEMENTS(s_apszHeaders), s_apszHeaders);
    if (RT_SUCCESS(rc))
    {
        char szUrl[_2K];
        RTStrPrintf(szUrl, sizeof(szUrl), "%s/%s/%s/%s", pszServer, pszFilename, pszCacheSubDir, pszFilename);

        /** @todo Use some temporary file name and rename it after the operation
         *        since not all systems support read-deny file sharing
         *        settings. */
        rtDbgCfgLog2(pThis, "Downloading '%s' to '%s'...\n", szUrl, pszPath);
        rc = RTHttpGetFile(hHttp, szUrl, pszPath);
        if (RT_FAILURE(rc))
        {
            RTFileDelete(pszPath);
            rtDbgCfgLog1(pThis, "%Rrc on URL '%s'\n", rc, pszPath);
        }
        if (rc == VERR_HTTP_NOT_FOUND)
        {
            /* Try the compressed version of the file. */
            pszPath[strlen(pszPath) - 1] = '_';
            szUrl[strlen(szUrl)     - 1] = '_';
            rtDbgCfgLog2(pThis, "Downloading '%s' to '%s'...\n", szUrl, pszPath);
            rc = RTHttpGetFile(hHttp, szUrl, pszPath);
            if (RT_SUCCESS(rc))
                rc = rtDbgCfgUnpackMsCacheFile(pThis, pszPath, pszFilename);
            else
            {
                rtDbgCfgLog1(pThis, "%Rrc on URL '%s'\n", rc, pszPath);
                RTFileDelete(pszPath);
            }
        }
    }

    RTHttpDestroy(hHttp);

    /*
     * If we succeeded, give it a try.
     */
    if (RT_SUCCESS(rc))
    {
        Assert(RTFileExists(pszPath));
        rtDbgCfgLog1(pThis, "Trying '%s'...\n", pszPath);
        rc = pfnCallback(pThis, pszPath, pvUser1, pvUser2);
        if (rc == VINF_CALLBACK_RETURN)
            rtDbgCfgLog1(pThis, "Found '%s'.\n", pszPath);
        else if (rc == VERR_CALLBACK_RETURN)
            rtDbgCfgLog1(pThis, "Error opening '%s'.\n", pszPath);
        else
            rtDbgCfgLog1(pThis, "Error %Rrc opening '%s'.\n", rc, pszPath);
    }

    return rc;

#else  /* !IPRT_WITH_HTTP */
    return VWRN_NOT_FOUND;
#endif /* !IPRT_WITH_HTTP */
}


static int rtDbgCfgCopyFileToCache(PRTDBGCFGINT pThis, char const *pszSrc, const char *pchCache, size_t cchCache,
                                   const char *pszCacheSubDir, PRTPATHSPLIT pSplitFn)
{
    if (!pszCacheSubDir || !*pszCacheSubDir)
        return VINF_SUCCESS;

    /** @todo copy to cache */
    return VINF_SUCCESS;
}


static int rtDbgCfgTryOpenCache(PRTDBGCFGINT pThis, char *pszPath, const char *pszCacheSubDir, PRTPATHSPLIT pSplitFn,
                                uint32_t fFlags, PFNDBGCFGOPEN pfnCallback, void *pvUser1, void *pvUser2)
{
    /*
     * If the cache doesn't exist, fail right away.
     */
    if (!pszCacheSubDir || !*pszCacheSubDir)
        return VWRN_NOT_FOUND;
    if (!RTDirExists(pszPath))
    {
        rtDbgCfgLog2(pThis, "Cache does not exist: '%s'\n", pszPath);
        return VWRN_NOT_FOUND;
    }

    size_t cchPath = strlen(pszPath);

    /*
     * Carefully construct the cache path with case insensitivity in mind.
     */
    bool const fCaseInsensitive = (fFlags & RTDBGCFG_O_CASE_INSENSITIVE)
                               && !rtDbgCfgIsFsCaseInsensitive(pszPath);
    const char *pszFilename = pSplitFn->apszComps[pSplitFn->cComps - 1];

    if (!rtDbgCfgIsDirAndFixCase(pszPath, pszFilename, fCaseInsensitive))
        return VWRN_NOT_FOUND;

    if (!rtDbgCfgIsDirAndFixCase(pszPath, pszCacheSubDir, fCaseInsensitive))
        return VWRN_NOT_FOUND;

    bool fProbablyCompressed = false;
    if (!rtDbgCfgIsFileAndFixCase(pszPath, pszFilename, fCaseInsensitive,
                                  RT_BOOL(fFlags & RTDBGCFG_O_MAYBE_COMPRESSED_MS), &fProbablyCompressed))
        return VWRN_NOT_FOUND;
    if (fProbablyCompressed)
    {
        int rc = rtDbgCfgUnpackMsCacheFile(pThis, pszPath, pszFilename);
        if (RT_FAILURE(rc))
            return VWRN_NOT_FOUND;
    }

    rtDbgCfgLog1(pThis, "Trying '%s'...\n", pszPath);
    int rc2 = pfnCallback(pThis, pszPath, pvUser1, pvUser2);
    if (rc2 == VINF_CALLBACK_RETURN)
        rtDbgCfgLog1(pThis, "Found '%s'.\n", pszPath);
    else if (rc2 == VERR_CALLBACK_RETURN)
        rtDbgCfgLog1(pThis, "Error opening '%s'.\n", pszPath);
    else
        rtDbgCfgLog1(pThis, "Error %Rrc opening '%s'.\n", rc2, pszPath);
    return rc2;
}


static int rtDbgCfgTryOpenList(PRTDBGCFGINT pThis, PRTLISTANCHOR pList, PRTPATHSPLIT pSplitFn, const char *pszCacheSubDir,
                               uint32_t fFlags, char *pszPath, PFNDBGCFGOPEN pfnCallback, void *pvUser1, void *pvUser2)
{
    int rcRet = VWRN_NOT_FOUND;
    int rc2;

    const char *pchCache = NULL;
    size_t      cchCache = 0;
    int         rcCache  = VWRN_NOT_FOUND;

    PRTDBGCFGSTR pCur;
    RTListForEach(pList, pCur, RTDBGCFGSTR, ListEntry)
    {
        size_t      cchDir = pCur->cch;
        const char *pszDir = pCur->sz;
        rtDbgCfgLog2(pThis, "Path list entry: '%s'\n", pszDir);

        /* This is very simplistic, but we have a unreasonably large path
           buffer, so it'll work just fine and simplify things greatly below. */
        if (cchDir >= RTPATH_MAX - 8U)
        {
            if (RT_SUCCESS_NP(rcRet))
                rcRet = VERR_FILENAME_TOO_LONG;
            continue;
        }

        /*
         * Process the path according to it's type.
         */
        if (!strncmp(pszDir, RT_STR_TUPLE("srv*")))
        {
            /*
             * Symbol server.
             */
            pszDir += sizeof("srv*") - 1;
            cchDir -= sizeof("srv*") - 1;
            bool        fSearchCache = false;
            const char *pszServer = (const char *)memchr(pszDir, '*', cchDir);
            if (!pszServer)
                pszServer = pszDir;
            else if (pszServer == pszDir)
                continue;
            {
                fSearchCache = true;
                pchCache = pszDir;
                cchCache = pszServer - pszDir;
                pszServer++;
            }

            /* We don't have any default cache directory, so skip if the cache is missing. */
            if (cchCache == 0)
                continue;

            /* Search the cache first (if we haven't already done so). */
            if (fSearchCache)
            {
                memcpy(pszPath, pchCache, cchCache);
                pszPath[cchCache] = '\0';
                RTPathChangeToUnixSlashes(pszPath, false);

                rcCache = rc2 = rtDbgCfgTryOpenCache(pThis, pszPath, pszCacheSubDir, pSplitFn, fFlags,
                                                     pfnCallback, pvUser1, pvUser2);
                if (rc2 == VINF_CALLBACK_RETURN || rc2 == VERR_CALLBACK_RETURN)
                    return rc2;
            }

            /* Try downloading the file. */
            if (rcCache == VWRN_NOT_FOUND)
            {
                memcpy(pszPath, pchCache, cchCache);
                pszPath[cchCache] = '\0';
                RTPathChangeToUnixSlashes(pszPath, false);

                rc2 = rtDbgCfgTryDownloadAndOpen(pThis, pszServer, pszPath, pszCacheSubDir, pSplitFn, fFlags,
                                                 pfnCallback, pvUser1, pvUser2);
                if (rc2 == VINF_CALLBACK_RETURN || rc2 == VERR_CALLBACK_RETURN)
                    return rc2;
            }
        }
        else if (!strncmp(pszDir, RT_STR_TUPLE("cache*")))
        {
            /*
             * Cache directory.
             */
            pszDir += sizeof("cache*") - 1;
            cchDir -= sizeof("cache*") - 1;
            if (!cchDir)
                continue;
            pchCache = pszDir;
            cchCache = cchDir;

            memcpy(pszPath, pchCache, cchCache);
            pszPath[cchCache] = '\0';
            RTPathChangeToUnixSlashes(pszPath, false);

            rcCache = rc2 = rtDbgCfgTryOpenCache(pThis, pszPath, pszCacheSubDir, pSplitFn, fFlags,
                                                 pfnCallback, pvUser1, pvUser2);
            if (rc2 == VINF_CALLBACK_RETURN || rc2 == VERR_CALLBACK_RETURN)
                return rc2;
        }
        else
        {
            /*
             * Normal directory. Check for our own 'rec*' and 'norec*' prefix
             * flags governing recursive searching.
             */
            uint32_t fFlagsDir = fFlags;
            if (!strncmp(pszDir, RT_STR_TUPLE("rec*")))
            {
                pszDir += sizeof("rec*") - 1;
                cchDir -= sizeof("rec*") - 1;
                fFlagsDir |= RTDBGCFG_O_RECURSIVE;
            }
            else if (!strncmp(pszDir, RT_STR_TUPLE("norec*")))
            {
                pszDir += sizeof("norec*") - 1;
                cchDir -= sizeof("norec*") - 1;
                fFlagsDir &= ~RTDBGCFG_O_RECURSIVE;
            }

            /* Copy the path into the buffer and do the searching. */
            memcpy(pszPath, pszDir, cchDir);
            pszPath[cchDir] = '\0';
            RTPathChangeToUnixSlashes(pszPath, false);

            rc2 = rtDbgCfgTryOpenDir(pThis, pszPath, pSplitFn, fFlagsDir, pfnCallback, pvUser1, pvUser2);
            if (rc2 == VINF_CALLBACK_RETURN || rc2 == VERR_CALLBACK_RETURN)
            {
                if (   rc2 == VINF_CALLBACK_RETURN
                    && cchCache > 0)
                    rtDbgCfgCopyFileToCache(pThis, pszPath, pchCache, cchCache, pszCacheSubDir, pSplitFn);
                return rc2;
            }
        }

        /* Propagate errors. */
        if (RT_FAILURE(rc2) && RT_SUCCESS_NP(rcRet))
            rcRet = rc2;
    }

    return rcRet;
}


/**
 * Common worker routine for Image and debug info opening.
 *
 * This will not search using for suffixes.
 *
 * @returns IPRT status code.
 * @param   hDbgCfg         The debugging configuration handle.  NIL_RTDBGCFG is
 *                          accepted, but the result is that no paths will be
 *                          searched beyond the given and the current directory.
 * @param   pszFilename     The filename to search for.  This may or may not
 *                          include a full or partial path.
 * @param   pszCacheSubDir  The cache subdirectory to look in.
 * @param   fFlags          Flags and hints.
 * @param   pfnCallback     The open callback routine.
 * @param   pvUser1         User parameter 1.
 * @param   pvUser2         User parameter 2.
 */
static int rtDbgCfgOpenWithSubDir(RTDBGCFG hDbgCfg, const char *pszFilename, const char *pszCacheSubDir,
                                  uint32_t fFlags, PFNDBGCFGOPEN pfnCallback, void *pvUser1, void *pvUser2)
{
    int rcRet = VINF_SUCCESS;
    int rc2;

    /*
     * Do a little validating first.
     */
    PRTDBGCFGINT pThis = hDbgCfg;
    if (pThis != NIL_RTDBGCFG)
        RTDBGCFG_VALID_RETURN_RC(pThis, VERR_INVALID_HANDLE);
    else
        pThis = NULL;
    AssertPtrReturn(pszFilename, VERR_INVALID_POINTER);
    AssertPtrReturn(pszCacheSubDir, VERR_INVALID_POINTER);
    AssertPtrReturn(pfnCallback, VERR_INVALID_POINTER);

    /*
     * Do some guessing as to the way we should parse the filename and whether
     * it's case exact or not.
     */
    bool fDosPath = strchr(pszFilename, ':')  != NULL
                 || strchr(pszFilename, '\\') != NULL
                 || RT_OPSYS_USES_DOS_PATHS(fFlags & RTDBGCFG_O_OPSYS_MASK)
                 || (fFlags & RTDBGCFG_O_CASE_INSENSITIVE);
    if (fDosPath)
        fFlags |= RTDBGCFG_O_CASE_INSENSITIVE;

    rtDbgCfgLog2(pThis, "Looking for '%s' w/ cache subdir '%s' and %#x flags...\n", pszFilename, pszCacheSubDir, fFlags);

    PRTPATHSPLIT pSplitFn;
    rc2 = RTPathSplitA(pszFilename, &pSplitFn, fDosPath ? RTPATH_STR_F_STYLE_DOS : RTPATH_STR_F_STYLE_UNIX);
    if (RT_FAILURE(rc2))
        return rc2;
    AssertReturnStmt(pSplitFn->fProps & RTPATH_PROP_FILENAME, RTPathSplitFree(pSplitFn), VERR_IS_A_DIRECTORY);

    /*
     * Try the stored file name first if it has a kind of absolute path.
     */
    char szPath[RTPATH_MAX];
    if (RTPATH_PROP_HAS_ROOT_SPEC(pSplitFn->fProps))
    {
        rc2 = RTPathSplitReassemble(pSplitFn, RTPATH_STR_F_STYLE_HOST, szPath, sizeof(szPath));
        if (RT_SUCCESS(rc2) && RTFileExists(szPath))
        {
            RTPathChangeToUnixSlashes(szPath, false);
            rtDbgCfgLog1(pThis, "Trying '%s'...\n", szPath);
            rc2 = pfnCallback(pThis, szPath, pvUser1, pvUser2);
            if (rc2 == VINF_CALLBACK_RETURN)
                rtDbgCfgLog1(pThis, "Found '%s'.\n", szPath);
            else if (rc2 == VERR_CALLBACK_RETURN)
                rtDbgCfgLog1(pThis, "Error opening '%s'.\n", szPath);
            else
                rtDbgCfgLog1(pThis, "Error %Rrc opening '%s'.\n", rc2, szPath);
        }
    }
    if (   rc2 != VINF_CALLBACK_RETURN
        && rc2 != VERR_CALLBACK_RETURN)
    {
        /*
         * Try the current directory (will take cover relative paths
         * skipped above).
         */
        rc2 = RTPathGetCurrent(szPath, sizeof(szPath));
        if (RT_FAILURE(rc2))
            strcpy(szPath, ".");
        RTPathChangeToUnixSlashes(szPath, false);

        rc2 = rtDbgCfgTryOpenDir(pThis, szPath, pSplitFn, fFlags, pfnCallback, pvUser1, pvUser2);
        if (RT_FAILURE(rc2) && RT_SUCCESS_NP(rcRet))
            rcRet = rc2;

        if (   rc2 != VINF_CALLBACK_RETURN
            && rc2 != VERR_CALLBACK_RETURN
            && pThis)
        {
            rc2 = RTCritSectRwEnterShared(&pThis->CritSect);
            if (RT_SUCCESS(rc2))
            {
                /*
                 * Run the applicable lists.
                 */
                rc2 = rtDbgCfgTryOpenList(pThis, &pThis->PathList, pSplitFn, pszCacheSubDir, fFlags, szPath,
                                          pfnCallback, pvUser1, pvUser2);
                if (RT_FAILURE(rc2) && RT_SUCCESS_NP(rcRet))
                    rcRet = rc2;

#ifdef RT_OS_WINDOWS
                if (   rc2 != VINF_CALLBACK_RETURN
                    && rc2 != VERR_CALLBACK_RETURN
                    && (fFlags & RTDBGCFG_O_EXECUTABLE_IMAGE)
                    && !(pThis->fFlags & RTDBGCFG_FLAGS_NO_SYSTEM_PATHS) )
                {
                    rc2 = rtDbgCfgTryOpenList(pThis, &pThis->NtExecutablePathList, pSplitFn, pszCacheSubDir, fFlags, szPath,
                                              pfnCallback, pvUser1, pvUser2);
                    if (RT_FAILURE(rc2) && RT_SUCCESS_NP(rcRet))
                        rcRet = rc2;
                }

                if (   rc2 != VINF_CALLBACK_RETURN
                    && rc2 != VERR_CALLBACK_RETURN
                    && !(pThis->fFlags & RTDBGCFG_FLAGS_NO_SYSTEM_PATHS) )
                {
                    rc2 = rtDbgCfgTryOpenList(pThis, &pThis->NtSymbolPathList, pSplitFn, pszCacheSubDir, fFlags, szPath,
                                              pfnCallback, pvUser1, pvUser2);
                    if (RT_FAILURE(rc2) && RT_SUCCESS_NP(rcRet))
                        rcRet = rc2;
                }
#endif
                RTCritSectRwLeaveShared(&pThis->CritSect);
            }
            else if (RT_SUCCESS(rcRet))
                rcRet = rc2;
        }
    }

    RTPathSplitFree(pSplitFn);
    if (   rc2 == VINF_CALLBACK_RETURN
        || rc2 == VERR_CALLBACK_RETURN)
        rcRet = rc2;
    else if (RT_SUCCESS(rcRet))
        rcRet = VERR_NOT_FOUND;
    return rcRet;
}


RTDECL(int) RTDbgCfgOpenPeImage(RTDBGCFG hDbgCfg, const char *pszFilename, uint32_t cbImage, uint32_t uTimestamp,
                                PFNDBGCFGOPEN pfnCallback, void *pvUser1, void *pvUser2)
{
    char szSubDir[32];
    RTStrPrintf(szSubDir, sizeof(szSubDir), "%08X%x", uTimestamp, cbImage);
    return rtDbgCfgOpenWithSubDir(hDbgCfg, pszFilename, szSubDir,
                                  RT_OPSYS_WINDOWS /* approx */ | RTDBGCFG_O_SYMSRV | RTDBGCFG_O_CASE_INSENSITIVE
                                  | RTDBGCFG_O_MAYBE_COMPRESSED_MS | RTDBGCFG_O_EXECUTABLE_IMAGE,
                                  pfnCallback, pvUser1, pvUser2);
}


RTDECL(int) RTDbgCfgOpenPdb70(RTDBGCFG hDbgCfg, const char *pszFilename, PCRTUUID pUuid, uint32_t uAge,
                              PFNDBGCFGOPEN pfnCallback, void *pvUser1, void *pvUser2)
{
    char szSubDir[64];
    if (!pUuid)
        szSubDir[0] = '\0';
    else
    {
        /* Stringify the UUID and remove the dashes. */
        int rc2 = RTUuidToStr(pUuid, szSubDir, sizeof(szSubDir));
        AssertRCReturn(rc2, rc2);

        char *pszSrc = szSubDir;
        char *pszDst = szSubDir;
        char ch;
        while ((ch = *pszSrc++))
            if (ch != '-')
                *pszDst++ = RT_C_TO_UPPER(ch);

        RTStrPrintf(pszDst, &szSubDir[sizeof(szSubDir)] - pszDst, "%X", uAge);
    }

    return rtDbgCfgOpenWithSubDir(hDbgCfg, pszFilename, szSubDir,
                                  RT_OPSYS_WINDOWS /* approx */ | RTDBGCFG_O_SYMSRV | RTDBGCFG_O_CASE_INSENSITIVE
                                  | RTDBGCFG_O_MAYBE_COMPRESSED_MS | RTDBGCFG_O_EXT_DEBUG_FILE,
                                  pfnCallback, pvUser1, pvUser2);
}


RTDECL(int) RTDbgCfgOpenPdb20(RTDBGCFG hDbgCfg, const char *pszFilename, uint32_t cbImage, uint32_t uTimestamp, uint32_t uAge,
                              PFNDBGCFGOPEN pfnCallback, void *pvUser1, void *pvUser2)
{
    /** @todo test this! */
    char szSubDir[32];
    RTStrPrintf(szSubDir, sizeof(szSubDir), "%08X%x", uTimestamp, uAge);
    return rtDbgCfgOpenWithSubDir(hDbgCfg, pszFilename, szSubDir,
                                  RT_OPSYS_WINDOWS /* approx */ | RTDBGCFG_O_SYMSRV | RTDBGCFG_O_CASE_INSENSITIVE
                                  | RTDBGCFG_O_MAYBE_COMPRESSED_MS | RTDBGCFG_O_EXT_DEBUG_FILE,
                                  pfnCallback, pvUser1, pvUser2);
}


RTDECL(int) RTDbgCfgOpenDbg(RTDBGCFG hDbgCfg, const char *pszFilename, uint32_t cbImage, uint32_t uTimestamp,
                            PFNDBGCFGOPEN pfnCallback, void *pvUser1, void *pvUser2)
{
    char szSubDir[32];
    RTStrPrintf(szSubDir, sizeof(szSubDir), "%08X%x", uTimestamp, cbImage);
    return rtDbgCfgOpenWithSubDir(hDbgCfg, pszFilename, szSubDir,
                                  RT_OPSYS_WINDOWS /* approx */ | RTDBGCFG_O_SYMSRV | RTDBGCFG_O_CASE_INSENSITIVE
                                  | RTDBGCFG_O_MAYBE_COMPRESSED_MS | RTDBGCFG_O_EXT_DEBUG_FILE,
                                  pfnCallback, pvUser1, pvUser2);
}


RTDECL(int) RTDbgCfgOpenDwo(RTDBGCFG hDbgCfg, const char *pszFilename, uint32_t uCrc32,
                            PFNDBGCFGOPEN pfnCallback, void *pvUser1, void *pvUser2)
{
    char szSubDir[32];
    RTStrPrintf(szSubDir, sizeof(szSubDir), "%08x", uCrc32);
    return rtDbgCfgOpenWithSubDir(hDbgCfg, pszFilename, szSubDir,
                                  RT_OPSYS_UNKNOWN | RTDBGCFG_O_EXT_DEBUG_FILE,
                                  pfnCallback, pvUser1, pvUser2);
}



/*
 *
 *  D a r w i n   . d S Y M   b u n d l e s
 *  D a r w i n   . d S Y M   b u n d l e s
 *  D a r w i n   . d S Y M   b u n d l e s
 *
 */

/**
 * Very similar to rtDbgCfgTryOpenDir.
 */
static int rtDbgCfgTryOpenDsymBundleInDir(PRTDBGCFGINT pThis, char *pszPath, PRTPATHSPLIT pSplitFn, const char *pszDsymName,
                                          uint32_t fFlags, PFNDBGCFGOPEN pfnCallback, void *pvUser1, void *pvUser2)
{
    int rcRet = VWRN_NOT_FOUND;
    int rc2;

    /* If the directory doesn't exist, just quit immediately.
       Note! Our case insensitivity doesn't extend to the search dirs themselfs,
             only to the bits under neath them. */
    if (!RTDirExists(pszPath))
    {
        rtDbgCfgLog2(pThis, "Dir does not exist: '%s'\n", pszPath);
        return rcRet;
    }

    /* Figure out whether we have to do a case sensitive search or not.
       Note! As a simplification, we don't ask for case settings in each
             directory under the user specified path, we assume the file
             systems that mounted there have compatible settings. Faster
             that way. */
    bool const fCaseInsensitive = (fFlags & RTDBGCFG_O_CASE_INSENSITIVE)
                               && !rtDbgCfgIsFsCaseInsensitive(pszPath);

    size_t const cchPath = strlen(pszPath);

    /*
     * Look for the file with less and less of the original path given.
     */
    for (unsigned i = RTPATH_PROP_HAS_ROOT_SPEC(pSplitFn->fProps); i < pSplitFn->cComps; i++)
    {
        pszPath[cchPath] = '\0';

        rc2 = VINF_SUCCESS;
        for (unsigned j = i; j < pSplitFn->cComps - 1U && RT_SUCCESS(rc2); j++)
            if (!rtDbgCfgIsDirAndFixCase(pszPath, pSplitFn->apszComps[i], fCaseInsensitive))
                rc2 = VERR_FILE_NOT_FOUND;
        if (    RT_SUCCESS(rc2)
            && !rtDbgCfgIsDirAndFixCase(pszPath, pszDsymName, fCaseInsensitive)
            && !rtDbgCfgIsDirAndFixCase(pszPath, "Contents", fCaseInsensitive)
            && !rtDbgCfgIsDirAndFixCase(pszPath, "Resources", fCaseInsensitive)
            && !rtDbgCfgIsDirAndFixCase(pszPath, "DWARF", fCaseInsensitive))
        {
            if (rtDbgCfgIsFileAndFixCase(pszPath, pSplitFn->apszComps[pSplitFn->cComps - 1], fCaseInsensitive, false, NULL))
            {
                rtDbgCfgLog1(pThis, "Trying '%s'...\n", pszPath);
                rc2 = pfnCallback(pThis, pszPath, pvUser1, pvUser2);
                if (rc2 == VINF_CALLBACK_RETURN || rc2 == VERR_CALLBACK_RETURN)
                {
                    if (rc2 == VINF_CALLBACK_RETURN)
                        rtDbgCfgLog1(pThis, "Found '%s'.\n", pszPath);
                    else
                        rtDbgCfgLog1(pThis, "Error opening '%s'.\n", pszPath);
                    return rc2;
                }
                rtDbgCfgLog1(pThis, "Error %Rrc opening '%s'.\n", rc2, pszPath);
                if (RT_FAILURE(rc2) && RT_SUCCESS_NP(rcRet))
                    rcRet = rc2;
            }
        }
        rc2 = VERR_FILE_NOT_FOUND;
    }

    /*
     * Do a recursive search if requested.
     */
    if (   (fFlags & RTDBGCFG_O_RECURSIVE)
        && !(pThis->fFlags & RTDBGCFG_FLAGS_NO_RECURSIV_SEARCH) )
    {
        /** @todo Recursive searching will be done later. */
    }

    return rcRet;
}


/**
 * Very similar to rtDbgCfgTryOpenList.
 */
static int rtDbgCfgTryOpenDsumBundleInList(PRTDBGCFGINT pThis, PRTLISTANCHOR pList, PRTPATHSPLIT pSplitFn,
                                           const char *pszDsymName, const char *pszCacheSubDir, uint32_t fFlags, char *pszPath,
                                           PFNDBGCFGOPEN pfnCallback, void *pvUser1, void *pvUser2)
{
    int rcRet = VWRN_NOT_FOUND;
    int rc2;

    const char *pchCache = NULL;
    size_t      cchCache = 0;
    int         rcCache  = VWRN_NOT_FOUND;

    PRTDBGCFGSTR pCur;
    RTListForEach(pList, pCur, RTDBGCFGSTR, ListEntry)
    {
        size_t      cchDir = pCur->cch;
        const char *pszDir = pCur->sz;
        rtDbgCfgLog2(pThis, "Path list entry: '%s'\n", pszDir);

        /* This is very simplistic, but we have a unreasonably large path
           buffer, so it'll work just fine and simplify things greatly below. */
        if (cchDir >= RTPATH_MAX - 8U)
        {
            if (RT_SUCCESS_NP(rcRet))
                rcRet = VERR_FILENAME_TOO_LONG;
            continue;
        }

        /*
         * Process the path according to it's type.
         */
        if (!strncmp(pszDir, RT_STR_TUPLE("srv*")))
        {
            /*
             * Symbol server.
             */
            pszDir += sizeof("srv*") - 1;
            cchDir -= sizeof("srv*") - 1;
            bool        fSearchCache = false;
            const char *pszServer = (const char *)memchr(pszDir, '*', cchDir);
            if (!pszServer)
                pszServer = pszDir;
            else if (pszServer == pszDir)
                continue;
            {
                fSearchCache = true;
                pchCache = pszDir;
                cchCache = pszServer - pszDir;
                pszServer++;
            }

            /* We don't have any default cache directory, so skip if the cache is missing. */
            if (cchCache == 0)
                continue;

            /* Search the cache first (if we haven't already done so). */
            if (fSearchCache)
            {
                memcpy(pszPath, pchCache, cchCache);
                pszPath[cchCache] = '\0';
                RTPathChangeToUnixSlashes(pszPath, false);

                rcCache = rc2 = rtDbgCfgTryOpenCache(pThis, pszPath, pszCacheSubDir, pSplitFn, fFlags,
                                                     pfnCallback, pvUser1, pvUser2);
                if (rc2 == VINF_CALLBACK_RETURN || rc2 == VERR_CALLBACK_RETURN)
                    return rc2;
            }

            /* Try downloading the file. */
            if (rcCache == VWRN_NOT_FOUND)
            {
                memcpy(pszPath, pchCache, cchCache);
                pszPath[cchCache] = '\0';
                RTPathChangeToUnixSlashes(pszPath, false);

                rc2 = rtDbgCfgTryDownloadAndOpen(pThis, pszServer, pszPath, pszCacheSubDir, pSplitFn, fFlags,
                                                 pfnCallback, pvUser1, pvUser2);
                if (rc2 == VINF_CALLBACK_RETURN || rc2 == VERR_CALLBACK_RETURN)
                    return rc2;
            }
        }
        else if (!strncmp(pszDir, RT_STR_TUPLE("cache*")))
        {
            /*
             * Cache directory.
             */
            pszDir += sizeof("cache*") - 1;
            cchDir -= sizeof("cache*") - 1;
            if (!cchDir)
                continue;
            pchCache = pszDir;
            cchCache = cchDir;

            memcpy(pszPath, pchCache, cchCache);
            pszPath[cchCache] = '\0';
            RTPathChangeToUnixSlashes(pszPath, false);

            rcCache = rc2 = rtDbgCfgTryOpenCache(pThis, pszPath, pszCacheSubDir, pSplitFn, fFlags,
                                                 pfnCallback, pvUser1, pvUser2);
            if (rc2 == VINF_CALLBACK_RETURN || rc2 == VERR_CALLBACK_RETURN)
                return rc2;
        }
        else
        {
            /*
             * Normal directory. Check for our own 'rec*' and 'norec*' prefix
             * flags governing recursive searching.
             */
            uint32_t fFlagsDir = fFlags;
            if (!strncmp(pszDir, RT_STR_TUPLE("rec*")))
            {
                pszDir += sizeof("rec*") - 1;
                cchDir -= sizeof("rec*") - 1;
                fFlagsDir |= RTDBGCFG_O_RECURSIVE;
            }
            else if (!strncmp(pszDir, RT_STR_TUPLE("norec*")))
            {
                pszDir += sizeof("norec*") - 1;
                cchDir -= sizeof("norec*") - 1;
                fFlagsDir &= ~RTDBGCFG_O_RECURSIVE;
            }

            /* Copy the path into the buffer and do the searching. */
            memcpy(pszPath, pszDir, cchDir);
            pszPath[cchDir] = '\0';
            RTPathChangeToUnixSlashes(pszPath, false);

            rc2 = rtDbgCfgTryOpenDsymBundleInDir(pThis, pszPath, pSplitFn, pszDsymName, fFlagsDir,
                                                 pfnCallback, pvUser1, pvUser2);
            if (rc2 == VINF_CALLBACK_RETURN || rc2 == VERR_CALLBACK_RETURN)
            {
                if (   rc2 == VINF_CALLBACK_RETURN
                    && cchCache > 0)
                    rtDbgCfgCopyFileToCache(pThis, pszPath, pchCache, cchCache, pszCacheSubDir, pSplitFn);
                return rc2;
            }
        }

        /* Propagate errors. */
        if (RT_FAILURE(rc2) && RT_SUCCESS_NP(rcRet))
            rcRet = rc2;
    }

    return rcRet;
}


RTDECL(int) RTDbgCfgOpenDsymBundle(RTDBGCFG hDbgCfg, const char *pszImage, PCRTUUID pUuid,
                                   PFNDBGCFGOPEN pfnCallback, void *pvUser1, void *pvUser2)
{
    /*
     * Bundles are directories, means we can forget about sharing code much
     * with the other RTDbgCfgOpenXXX methods.  Thus we're duplicating a lot of
     * code from rtDbgCfgOpenWithSubDir with .dSYM related adjustments, so, a bug
     * found here or there probably means the other version needs updating.
     */
    int rcRet = VINF_SUCCESS;
    int rc2;

    /*
     * Do a little validating first.
     */
    PRTDBGCFGINT pThis = hDbgCfg;
    if (pThis != NIL_RTDBGCFG)
        RTDBGCFG_VALID_RETURN_RC(pThis, VERR_INVALID_HANDLE);
    else
        pThis = NULL;
    AssertPtrReturn(pszImage, VERR_INVALID_POINTER);
    AssertPtrReturn(pfnCallback, VERR_INVALID_POINTER);

    /*
     * Set up rtDbgCfgOpenWithSubDir parameters.
     */
    uint32_t fFlags = RTDBGCFG_O_EXT_DEBUG_FILE | RT_OPSYS_DARWIN;
    const char *pszCacheSubDir = NULL;
    char szCacheSubDir[RTUUID_STR_LENGTH];
    if (pUuid)
    {
        RTUuidToStr(pUuid, szCacheSubDir, sizeof(szCacheSubDir));
        pszCacheSubDir = szCacheSubDir;
    }

    /*
     * Do some guessing as to the way we should parse the filename and whether
     * it's case exact or not.
     */
    bool fDosPath = strchr(pszImage, ':')  != NULL
                 || strchr(pszImage, '\\') != NULL
                 || RT_OPSYS_USES_DOS_PATHS(fFlags & RTDBGCFG_O_OPSYS_MASK)
                 || (fFlags & RTDBGCFG_O_CASE_INSENSITIVE);
    if (fDosPath)
        fFlags |= RTDBGCFG_O_CASE_INSENSITIVE;

    rtDbgCfgLog2(pThis, "Looking for '%s' with %#x flags...\n", pszImage, fFlags);

    PRTPATHSPLIT pSplitFn;
    rc2 = RTPathSplitA(pszImage, &pSplitFn, fDosPath ? RTPATH_STR_F_STYLE_DOS : RTPATH_STR_F_STYLE_UNIX);
    if (RT_FAILURE(rc2))
        return rc2;
    AssertReturnStmt(pSplitFn->fProps & RTPATH_PROP_FILENAME, RTPathSplitFree(pSplitFn), VERR_IS_A_DIRECTORY);

    /*
     * Try the image directory first.
     */
    char szPath[RTPATH_MAX];
    if (pSplitFn->cComps > 0)
    {
        rc2 = RTPathSplitReassemble(pSplitFn, RTPATH_STR_F_STYLE_HOST, szPath, sizeof(szPath));
        if (RT_SUCCESS(rc2))
            rc2 = RTStrCat(szPath, sizeof(szPath),
                           ".dSYM" RTPATH_SLASH_STR "Contents" RTPATH_SLASH_STR "Resources" RTPATH_SLASH_STR "DWARF");
        if (RT_SUCCESS(rc2))
            rc2 = RTPathAppend(szPath, sizeof(szPath), pSplitFn->apszComps[pSplitFn->cComps - 1]);
        if (RT_SUCCESS(rc2))
        {
            RTPathChangeToUnixSlashes(szPath, false);
            rtDbgCfgLog1(pThis, "Trying '%s'...\n", szPath);
            rc2 = pfnCallback(pThis, szPath, pvUser1, pvUser2);
            if (rc2 == VINF_CALLBACK_RETURN)
                rtDbgCfgLog1(pThis, "Found '%s'.\n", szPath);
            else if (rc2 == VERR_CALLBACK_RETURN)
                rtDbgCfgLog1(pThis, "Error opening '%s'.\n", szPath);
            else
                rtDbgCfgLog1(pThis, "Error %Rrc opening '%s'.\n", rc2, szPath);
        }
    }
    if (   rc2 != VINF_CALLBACK_RETURN
        && rc2 != VERR_CALLBACK_RETURN)
    {
        char *pszDsymName = (char *)alloca(strlen(pSplitFn->apszComps[pSplitFn->cComps - 1]) + sizeof(".dSYM"));
        strcat(strcpy(pszDsymName, pSplitFn->apszComps[pSplitFn->cComps - 1]), ".dSYM");

        /*
         * Try the current directory (will take cover relative paths
         * skipped above).
         */
        rc2 = RTPathGetCurrent(szPath, sizeof(szPath));
        if (RT_FAILURE(rc2))
            strcpy(szPath, ".");
        RTPathChangeToUnixSlashes(szPath, false);

        rc2 = rtDbgCfgTryOpenDsymBundleInDir(pThis, szPath, pSplitFn, pszDsymName, fFlags, pfnCallback, pvUser1, pvUser2);
        if (RT_FAILURE(rc2) && RT_SUCCESS_NP(rcRet))
            rcRet = rc2;

        if (   rc2 != VINF_CALLBACK_RETURN
            && rc2 != VERR_CALLBACK_RETURN
            && pThis)
        {
            rc2 = RTCritSectRwEnterShared(&pThis->CritSect);
            if (RT_SUCCESS(rc2))
            {
                /*
                 * Run the applicable lists.
                 */
                rc2 = rtDbgCfgTryOpenDsumBundleInList(pThis, &pThis->PathList, pSplitFn, pszDsymName,
                                                      pszCacheSubDir, fFlags, szPath,
                                                      pfnCallback, pvUser1, pvUser2);
                if (RT_FAILURE(rc2) && RT_SUCCESS_NP(rcRet))
                    rcRet = rc2;

                RTCritSectRwLeaveShared(&pThis->CritSect);
            }
            else if (RT_SUCCESS(rcRet))
                rcRet = rc2;
        }
    }

    RTPathSplitFree(pSplitFn);
    if (   rc2 == VINF_CALLBACK_RETURN
        || rc2 == VERR_CALLBACK_RETURN)
        rcRet = rc2;
    else if (RT_SUCCESS(rcRet))
        rcRet = VERR_NOT_FOUND;
    return rcRet;
}



RTDECL(int) RTDbgCfgSetLogCallback(RTDBGCFG hDbgCfg, PFNRTDBGCFGLOG pfnCallback, void *pvUser)
{
    PRTDBGCFGINT pThis = hDbgCfg;
    RTDBGCFG_VALID_RETURN_RC(pThis, VERR_INVALID_HANDLE);
    AssertPtrNullReturn(pfnCallback, VERR_INVALID_POINTER);

    int rc = RTCritSectRwEnterExcl(&pThis->CritSect);
    if (RT_SUCCESS(rc))
    {
        if (   pThis->pfnLogCallback == NULL
            || pfnCallback == NULL
            || pfnCallback == pThis->pfnLogCallback)
        {
            pThis->pfnLogCallback = NULL;
            pThis->pvLogUser      = NULL;
            ASMCompilerBarrier(); /* paranoia */
            pThis->pvLogUser      = pvUser;
            pThis->pfnLogCallback = pfnCallback;
            rc = VINF_SUCCESS;
        }
        else
            rc = VERR_ACCESS_DENIED;
        RTCritSectRwLeaveExcl(&pThis->CritSect);
    }

    return rc;
}


/**
 * Frees a string list.
 *
 * @param   pList               The list to free.
 */
static void rtDbgCfgFreeStrList(PRTLISTANCHOR pList)
{
    PRTDBGCFGSTR pCur;
    PRTDBGCFGSTR pNext;
    RTListForEachSafe(pList, pCur, pNext, RTDBGCFGSTR, ListEntry)
    {
        RTListNodeRemove(&pCur->ListEntry);
        RTMemFree(pCur);
    }
}


/**
 * Make changes to a string list, given a semicolon separated input string.
 *
 * @returns VINF_SUCCESS, VERR_FILENAME_TOO_LONG, VERR_NO_MEMORY
 * @param   pThis               The config instance.
 * @param   enmOp               The change operation.
 * @param   pszValue            The input strings separated by semicolon.
 * @param   fPaths              Indicates that this is a path list and that we
 *                              should look for srv and cache prefixes.
 * @param   pList               The string list anchor.
 */
static int rtDbgCfgChangeStringList(PRTDBGCFGINT pThis, RTDBGCFGOP enmOp, const char *pszValue, bool fPaths,
                                    PRTLISTANCHOR pList)
{
    if (enmOp == RTDBGCFGOP_SET)
        rtDbgCfgFreeStrList(pList);

    while (*pszValue)
    {
        /* Skip separators. */
        while (*pszValue == ';')
            pszValue++;
        if (!*pszValue)
            break;

        /* Find the end of this path. */
        const char *pchPath = pszValue++;
        char ch;
        while ((ch = *pszValue) && ch != ';')
            pszValue++;
        size_t cchPath = pszValue - pchPath;
        if (cchPath >= UINT16_MAX)
            return VERR_FILENAME_TOO_LONG;

        if (enmOp == RTDBGCFGOP_REMOVE)
        {
            /*
             * Remove all occurences.
             */
            PRTDBGCFGSTR pCur;
            PRTDBGCFGSTR pNext;
            RTListForEachSafe(pList, pCur, pNext, RTDBGCFGSTR, ListEntry)
            {
                if (   pCur->cch == cchPath
                    && !memcmp(pCur->sz, pchPath, cchPath))
                {
                    RTListNodeRemove(&pCur->ListEntry);
                    RTMemFree(pCur);
                }
            }
        }
        else
        {
            /*
             * We're adding a new one.
             */
            PRTDBGCFGSTR pNew = (PRTDBGCFGSTR)RTMemAlloc(RT_OFFSETOF(RTDBGCFGSTR, sz[cchPath + 1]));
            if (!pNew)
                return VERR_NO_MEMORY;
            pNew->cch = (uint16_t)cchPath;
            pNew->fFlags = 0;
            memcpy(pNew->sz, pchPath, cchPath);
            pNew->sz[cchPath] = '\0';

            if (enmOp == RTDBGCFGOP_PREPEND)
                RTListPrepend(pList, &pNew->ListEntry);
            else
                RTListAppend(pList, &pNew->ListEntry);
        }
    }

    return VINF_SUCCESS;
}


/**
 * Make changes to a 64-bit value
 *
 * @returns VINF_SUCCESS, VERR_DBG_CFG_INVALID_VALUE.
 * @param   pThis               The config instance.
 * @param   enmOp               The change operation.
 * @param   pszValue            The input value.
 * @param   pszMnemonics        The mnemonics map for this value.
 * @param   puValue             The value to change.
 */
static int rtDbgCfgChangeStringU64(PRTDBGCFGINT pThis, RTDBGCFGOP enmOp, const char *pszValue,
                                   PCRTDBGCFGU64MNEMONIC paMnemonics, uint64_t *puValue)
{
    uint64_t    uNew = enmOp == RTDBGCFGOP_SET ? 0 : *puValue;

    char        ch;
    while ((ch = *pszValue))
    {
        /* skip whitespace and separators */
        while (RT_C_IS_SPACE(ch) || RT_C_IS_CNTRL(ch) || ch == ';' || ch == ':')
            ch = *++pszValue;
        if (!ch)
            break;

        if (RT_C_IS_DIGIT(ch))
        {
            uint64_t uTmp;
            int rc = RTStrToUInt64Ex(pszValue, (char **)&pszValue, 0, &uTmp);
            if (RT_FAILURE(rc) || rc == VWRN_NUMBER_TOO_BIG)
                return VERR_DBG_CFG_INVALID_VALUE;

            if (enmOp != RTDBGCFGOP_REMOVE)
                uNew |= uTmp;
            else
                uNew &= ~uTmp;
        }
        else
        {
            /* A mnemonic, find the end of it. */
            const char *pszMnemonic = pszValue - 1;
            do
                ch = *++pszValue;
            while (ch && !RT_C_IS_SPACE(ch) && !RT_C_IS_CNTRL(ch) && ch != ';' && ch != ':');
            size_t cchMnemonic = pszValue - pszMnemonic;

            /* Look it up in the map and apply it. */
            unsigned i = 0;
            while (paMnemonics[i].pszMnemonic)
            {
                if (   cchMnemonic == paMnemonics[i].cchMnemonic
                    && !memcmp(pszMnemonic, paMnemonics[i].pszMnemonic, cchMnemonic))
                {
                    if (paMnemonics[i].fSet ? enmOp != RTDBGCFGOP_REMOVE : enmOp == RTDBGCFGOP_REMOVE)
                        uNew |= paMnemonics[i].fFlags;
                    else
                        uNew &= ~paMnemonics[i].fFlags;
                   break;
                }
                i++;
            }

            if (!paMnemonics[i].pszMnemonic)
                return VERR_DBG_CFG_INVALID_VALUE;
        }
    }

    *puValue = uNew;
    return VINF_SUCCESS;
}


RTDECL(int) RTDbgCfgChangeString(RTDBGCFG hDbgCfg, RTDBGCFGPROP enmProp, RTDBGCFGOP enmOp, const char *pszValue)
{
    PRTDBGCFGINT pThis = hDbgCfg;
    RTDBGCFG_VALID_RETURN_RC(pThis, VERR_INVALID_HANDLE);
    AssertReturn(enmProp > RTDBGCFGPROP_INVALID && enmProp < RTDBGCFGPROP_END, VERR_INVALID_PARAMETER);
    AssertReturn(enmOp   > RTDBGCFGOP_INVALID   && enmOp   < RTDBGCFGOP_END,   VERR_INVALID_PARAMETER);
    if (!pszValue)
        pszValue = "";
    else
        AssertPtrReturn(pszValue, VERR_INVALID_POINTER);

    int rc = RTCritSectRwEnterExcl(&pThis->CritSect);
    if (RT_SUCCESS(rc))
    {
        switch (enmProp)
        {
            case RTDBGCFGPROP_FLAGS:
                rc = rtDbgCfgChangeStringU64(pThis, enmOp, pszValue, g_aDbgCfgFlags, &pThis->fFlags);
                break;
            case RTDBGCFGPROP_PATH:
                rc = rtDbgCfgChangeStringList(pThis, enmOp, pszValue, true, &pThis->PathList);
                break;
            case RTDBGCFGPROP_SUFFIXES:
                rc = rtDbgCfgChangeStringList(pThis, enmOp, pszValue, false, &pThis->SuffixList);
                break;
            case RTDBGCFGPROP_SRC_PATH:
                rc = rtDbgCfgChangeStringList(pThis, enmOp, pszValue, true, &pThis->SrcPathList);
                break;
            default:
                AssertFailed();
                rc = VERR_INTERNAL_ERROR_3;
        }

        RTCritSectRwLeaveExcl(&pThis->CritSect);
    }

    return rc;
}


RTDECL(int) RTDbgCfgChangeUInt(RTDBGCFG hDbgCfg, RTDBGCFGPROP enmProp, RTDBGCFGOP enmOp, uint64_t uValue)
{
    PRTDBGCFGINT pThis = hDbgCfg;
    RTDBGCFG_VALID_RETURN_RC(pThis, VERR_INVALID_HANDLE);
    AssertReturn(enmProp > RTDBGCFGPROP_INVALID && enmProp < RTDBGCFGPROP_END, VERR_INVALID_PARAMETER);
    AssertReturn(enmOp   > RTDBGCFGOP_INVALID   && enmOp   < RTDBGCFGOP_END,   VERR_INVALID_PARAMETER);

    int rc = RTCritSectRwEnterExcl(&pThis->CritSect);
    if (RT_SUCCESS(rc))
    {
        uint64_t *puValue = NULL;
        switch (enmProp)
        {
            case RTDBGCFGPROP_FLAGS:
                puValue = &pThis->fFlags;
                break;
            default:
                rc = VERR_DBG_CFG_NOT_UINT_PROP;
        }
        if (RT_SUCCESS(rc))
        {
            switch (enmOp)
            {
                case RTDBGCFGOP_SET:
                    *puValue = uValue;
                    break;
                case RTDBGCFGOP_APPEND:
                case RTDBGCFGOP_PREPEND:
                    *puValue |= uValue;
                    break;
                case RTDBGCFGOP_REMOVE:
                    *puValue &= ~uValue;
                    break;
                default:
                    AssertFailed();
                    rc = VERR_INTERNAL_ERROR_2;
            }
        }

        RTCritSectRwLeaveExcl(&pThis->CritSect);
    }

    return rc;
}


/**
 * Querys a string list as a single string (semicolon separators).
 *
 * @returns VINF_SUCCESS, VERR_BUFFER_OVERFLOW.
 * @param   pThis               The config instance.
 * @param   pList               The string list anchor.
 * @param   pszValue            The output buffer.
 * @param   cbValue             The size of the output buffer.
 */
static int rtDbgCfgQueryStringList(RTDBGCFG hDbgCfg, PRTLISTANCHOR pList,
                                   char *pszValue, size_t cbValue)
{
    /*
     * Check the length first.
     */
    size_t cbReq = 1;
    PRTDBGCFGSTR pCur;
    RTListForEach(pList, pCur, RTDBGCFGSTR, ListEntry)
        cbReq += pCur->cch + 1;
    if (cbReq > cbValue)
        return VERR_BUFFER_OVERFLOW;

    /*
     * Construct the string list in the buffer.
     */
    char *psz = pszValue;
    RTListForEach(pList, pCur, RTDBGCFGSTR, ListEntry)
    {
        if (psz != pszValue)
            *psz++ = ';';
        memcpy(psz, pCur->sz, pCur->cch);
        psz += pCur->cch;
    }
    *psz = '\0';

    return VINF_SUCCESS;
}


/**
 * Querys the string value of a 64-bit unsigned int.
 *
 * @returns VINF_SUCCESS, VERR_BUFFER_OVERFLOW.
 * @param   pThis               The config instance.
 * @param   uValue              The value to query.
 * @param   pszMnemonics        The mnemonics map for this value.
 * @param   pszValue            The output buffer.
 * @param   cbValue             The size of the output buffer.
 */
static int rtDbgCfgQueryStringU64(RTDBGCFG hDbgCfg, uint64_t uValue, PCRTDBGCFGU64MNEMONIC paMnemonics,
                                  char *pszValue, size_t cbValue)
{
    /*
     * If no mnemonics, just return the hex value.
     */
    if (!paMnemonics || paMnemonics[0].pszMnemonic)
    {
        char szTmp[64];
        size_t cch = RTStrPrintf(szTmp, sizeof(szTmp), "%#x", uValue);
        if (cch + 1 > cbValue)
            return VERR_BUFFER_OVERFLOW;
        memcpy(pszValue, szTmp, cbValue);
        return VINF_SUCCESS;
    }

    /*
     * Check that there is sufficient buffer space first.
     */
    size_t cbReq = 1;
    for (unsigned i = 0; paMnemonics[i].pszMnemonic; i++)
        if (  paMnemonics[i].fSet
            ? (paMnemonics[i].fFlags & uValue)
            : !(paMnemonics[i].fFlags & uValue))
            cbReq += (cbReq != 1) + paMnemonics[i].cchMnemonic;
    if (cbReq > cbValue)
        return VERR_BUFFER_OVERFLOW;

    /*
     * Construct the string.
     */
    char *psz = pszValue;
    for (unsigned i = 0; paMnemonics[i].pszMnemonic; i++)
        if (  paMnemonics[i].fSet
            ? (paMnemonics[i].fFlags & uValue)
            : !(paMnemonics[i].fFlags & uValue))
        {
            if (psz != pszValue)
                *psz++ = ' ';
            memcpy(psz, paMnemonics[i].pszMnemonic, paMnemonics[i].cchMnemonic);
            psz += paMnemonics[i].cchMnemonic;
        }
    *psz = '\0';
    return VINF_SUCCESS;
}


RTDECL(int) RTDbgCfgQueryString(RTDBGCFG hDbgCfg, RTDBGCFGPROP enmProp, char *pszValue, size_t cbValue)
{
    PRTDBGCFGINT pThis = hDbgCfg;
    RTDBGCFG_VALID_RETURN_RC(pThis, VERR_INVALID_HANDLE);
    AssertReturn(enmProp > RTDBGCFGPROP_INVALID && enmProp < RTDBGCFGPROP_END, VERR_INVALID_PARAMETER);
    AssertPtrReturn(pszValue, VERR_INVALID_POINTER);

    int rc = RTCritSectRwEnterShared(&pThis->CritSect);
    if (RT_SUCCESS(rc))
    {
        switch (enmProp)
        {
            case RTDBGCFGPROP_FLAGS:
                rc = rtDbgCfgQueryStringU64(pThis, pThis->fFlags, g_aDbgCfgFlags, pszValue, cbValue);
                break;
            case RTDBGCFGPROP_PATH:
                rc = rtDbgCfgQueryStringList(pThis, &pThis->PathList, pszValue, cbValue);
                break;
            case RTDBGCFGPROP_SUFFIXES:
                rc = rtDbgCfgQueryStringList(pThis, &pThis->SuffixList, pszValue, cbValue);
                break;
            case RTDBGCFGPROP_SRC_PATH:
                rc = rtDbgCfgQueryStringList(pThis, &pThis->SrcPathList, pszValue, cbValue);
                break;
            default:
                AssertFailed();
                rc = VERR_INTERNAL_ERROR_3;
        }

        RTCritSectRwLeaveShared(&pThis->CritSect);
    }

    return rc;
}


RTDECL(int) RTDbgCfgQueryUInt(RTDBGCFG hDbgCfg, RTDBGCFGPROP enmProp, uint64_t *puValue)
{
    PRTDBGCFGINT pThis = hDbgCfg;
    RTDBGCFG_VALID_RETURN_RC(pThis, VERR_INVALID_HANDLE);
    AssertReturn(enmProp > RTDBGCFGPROP_INVALID && enmProp < RTDBGCFGPROP_END, VERR_INVALID_PARAMETER);
    AssertPtrReturn(puValue, VERR_INVALID_POINTER);

    int rc = RTCritSectRwEnterShared(&pThis->CritSect);
    if (RT_SUCCESS(rc))
    {
        switch (enmProp)
        {
            case RTDBGCFGPROP_FLAGS:
                *puValue = pThis->fFlags;
                break;
            default:
                rc = VERR_DBG_CFG_NOT_UINT_PROP;
        }

        RTCritSectRwLeaveShared(&pThis->CritSect);
    }

    return rc;
}

RTDECL(uint32_t) RTDbgCfgRetain(RTDBGCFG hDbgCfg)
{
    PRTDBGCFGINT pThis = hDbgCfg;
    RTDBGCFG_VALID_RETURN_RC(pThis, UINT32_MAX);

    uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs);
    Assert(cRefs < UINT32_MAX / 2);
    return cRefs;
}


RTDECL(uint32_t) RTDbgCfgRelease(RTDBGCFG hDbgCfg)
{
    if (hDbgCfg == NIL_RTDBGCFG)
        return 0;

    PRTDBGCFGINT pThis = hDbgCfg;
    RTDBGCFG_VALID_RETURN_RC(pThis, UINT32_MAX);

    uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs);
    if (!cRefs)
    {
        /*
         * Last reference - free all memory.
         */
        ASMAtomicWriteU32(&pThis->u32Magic, ~RTDBGCFG_MAGIC);
        rtDbgCfgFreeStrList(&pThis->PathList);
        rtDbgCfgFreeStrList(&pThis->SuffixList);
        rtDbgCfgFreeStrList(&pThis->SrcPathList);
#ifdef RT_OS_WINDOWS
        rtDbgCfgFreeStrList(&pThis->NtSymbolPathList);
        rtDbgCfgFreeStrList(&pThis->NtExecutablePathList);
        rtDbgCfgFreeStrList(&pThis->NtSourcePath);
#endif
        RTCritSectRwDelete(&pThis->CritSect);
        RTMemFree(pThis);
    }
    else
        Assert(cRefs < UINT32_MAX / 2);
    return cRefs;
}


RTDECL(int) RTDbgCfgCreate(PRTDBGCFG phDbgCfg, const char *pszEnvVarPrefix, bool fNativePaths)
{
    /*
     * Validate input.
     */
    AssertPtrReturn(phDbgCfg, VERR_INVALID_POINTER);
    if (pszEnvVarPrefix)
    {
        AssertPtrReturn(pszEnvVarPrefix, VERR_INVALID_POINTER);
        AssertReturn(*pszEnvVarPrefix, VERR_INVALID_PARAMETER);
    }

    /*
     * Allocate and initialize a new instance.
     */
    PRTDBGCFGINT pThis = (PRTDBGCFGINT)RTMemAllocZ(sizeof(*pThis));
    if (!pThis)
        return VERR_NO_MEMORY;

    pThis->u32Magic   = RTDBGCFG_MAGIC;
    pThis->cRefs      = 1;
    RTListInit(&pThis->PathList);
    RTListInit(&pThis->SuffixList);
    RTListInit(&pThis->SrcPathList);
#ifdef RT_OS_WINDOWS
    RTListInit(&pThis->NtSymbolPathList);
    RTListInit(&pThis->NtExecutablePathList);
    RTListInit(&pThis->NtSourcePath);
#endif

    int rc = RTCritSectRwInit(&pThis->CritSect);
    if (RT_FAILURE(rc))
    {
        RTMemFree(pThis);
        return rc;
    }

    /*
     * Read configurtion from the environment if requested to do so.
     */
    if (pszEnvVarPrefix || fNativePaths)
    {
        const size_t cbEnvVar = 256;
        const size_t cbEnvVal = 65536 - cbEnvVar;
        char        *pszEnvVar = (char *)RTMemTmpAlloc(cbEnvVar + cbEnvVal);
        if (pszEnvVar)
        {
            char *pszEnvVal = pszEnvVar + cbEnvVar;

            if (pszEnvVarPrefix)
            {
                static struct
                {
                    RTDBGCFGPROP    enmProp;
                    const char     *pszVar;
                } const s_aProps[] =
                {
                    { RTDBGCFGPROP_FLAGS,       "FLAGS"    },
                    { RTDBGCFGPROP_PATH,        "PATH"     },
                    { RTDBGCFGPROP_SUFFIXES,    "SUFFIXES" },
                    { RTDBGCFGPROP_SRC_PATH,    "SRC_PATH" },
                };

                for (unsigned i = 0; i < RT_ELEMENTS(s_aProps); i++)
                {
                    size_t cchEnvVar = RTStrPrintf(pszEnvVar, cbEnvVar, "%s_%s", pszEnvVarPrefix, s_aProps[i].pszVar);
                    if (cchEnvVar >= cbEnvVar - 1)
                    {
                        rc = VERR_BUFFER_OVERFLOW;
                        break;
                    }

                    rc = RTEnvGetEx(RTENV_DEFAULT, pszEnvVar, pszEnvVal, cbEnvVal, NULL);
                    if (RT_SUCCESS(rc))
                    {
                        rc = RTDbgCfgChangeString(pThis, s_aProps[i].enmProp, RTDBGCFGOP_SET, pszEnvVal);
                        if (RT_FAILURE(rc))
                            break;
                    }
                    else if (rc != VERR_ENV_VAR_NOT_FOUND)
                        break;
                    else
                        rc = VINF_SUCCESS;
                }
            }

            /*
             * Pick up system specific search paths.
             */
            if (RT_SUCCESS(rc) && fNativePaths)
            {
                struct
                {
                    PRTLISTANCHOR   pList;
                    const char     *pszVar;
                    char            chSep;
                } aNativePaths[] =
                {
#ifdef RT_OS_WINDOWS
                    { &pThis->NtExecutablePathList, "_NT_EXECUTABLE_PATH",  ';' },
                    { &pThis->NtSymbolPathList,     "_NT_ALT_SYMBOL_PATH",  ';' },
                    { &pThis->NtSymbolPathList,     "_NT_SYMBOL_PATH",      ';' },
                    { &pThis->NtSourcePath,         "_NT_SOURCE_PATH",      ';' },
#endif
                    { NULL, NULL, 0 }
                };
                for (unsigned i = 0; aNativePaths[i].pList; i++)
                {
                    Assert(aNativePaths[i].chSep == ';'); /* fix when needed */
                    rc = RTEnvGetEx(RTENV_DEFAULT, aNativePaths[i].pszVar, pszEnvVal, cbEnvVal, NULL);
                    if (RT_SUCCESS(rc))
                    {
                        rc = rtDbgCfgChangeStringList(pThis, RTDBGCFGOP_APPEND, pszEnvVal, true, aNativePaths[i].pList);
                        if (RT_FAILURE(rc))
                            break;
                    }
                    else if (rc != VERR_ENV_VAR_NOT_FOUND)
                        break;
                    else
                        rc = VINF_SUCCESS;
                }
            }
            RTMemTmpFree(pszEnvVar);
        }
        else
            rc = VERR_NO_TMP_MEMORY;
        if (RT_FAILURE(rc))
        {
            /*
             * Error, bail out.
             */
            RTDbgCfgRelease(pThis);
            return rc;
        }
    }

    /*
     * Returns successfully.
     */
    *phDbgCfg = pThis;

    return VINF_SUCCESS;
}

