/* $Id: env-generic.cpp $ */
/** @file
 * IPRT - Environment, Generic.
 */

/*
 * Copyright (C) 2006-2023 Oracle and/or its affiliates.
 *
 * This file is part of VirtualBox base platform packages, as
 * available from https://www.virtualbox.org.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation, in version 3 of the
 * License.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see <https://www.gnu.org/licenses>.
 *
 * The contents of this file may alternatively be used under the terms
 * of the Common Development and Distribution License Version 1.0
 * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
 * in the VirtualBox 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.
 *
 * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
 */


/*********************************************************************************************************************************
*   Header Files                                                                                                                 *
*********************************************************************************************************************************/
#include <iprt/env.h>
#include "internal/iprt.h"

#include <iprt/assert.h>
#include <iprt/alloc.h>
#include <iprt/alloca.h>
#include <iprt/err.h>
#include <iprt/sort.h>
#include <iprt/string.h>
#include <iprt/utf16.h>
#include "internal/magics.h"

#ifdef RT_OS_WINDOWS
# include <iprt/nt/nt.h>
#else
# include <stdlib.h>
# if !defined(RT_OS_WINDOWS)
#  include <unistd.h>
# endif
# ifdef RT_OS_DARWIN
#  include <crt_externs.h>
# endif
# if defined(RT_OS_SOLARIS) || defined(RT_OS_FREEBSD) || defined(RT_OS_NETBSD) || defined(RT_OS_OPENBSD)
RT_C_DECLS_BEGIN
extern char **environ;
RT_C_DECLS_END
# endif
#endif


/*********************************************************************************************************************************
*   Defined Constants And Macros                                                                                                 *
*********************************************************************************************************************************/
/** The allocation granularity of the RTENVINTERNAL::papszEnv memory. */
#define RTENV_GROW_SIZE     16

/** Macro that unlocks the specified environment block. */
#define RTENV_LOCK(pEnvInt)     do { } while (0)
/** Macro that unlocks the specified environment block. */
#define RTENV_UNLOCK(pEnvInt)   do { } while (0)

/** @def RTENV_IMPLEMENTS_UTF8_DEFAULT_ENV_API
 * Indicates the RTEnv*Utf8 APIs are implemented. */
#if defined(RT_OS_WINDOWS) || defined(DOXYGEN_RUNNING)
# define RTENV_IMPLEMENTS_UTF8_DEFAULT_ENV_API 1
#endif

/** @def RTENV_ALLOW_EQUAL_FIRST_IN_VAR
 * Allows a variable to start with an '=' sign by default.  This is used by
 * windows to maintain CWDs of non-current drives.
 * @note Not supported by _wputenv AFAIK. */
#if defined(RT_OS_WINDOWS) || defined(DOXYGEN_RUNNING)
# define RTENV_ALLOW_EQUAL_FIRST_IN_VAR 1
#endif


/*********************************************************************************************************************************
*   Structures and Typedefs                                                                                                      *
*********************************************************************************************************************************/
/**
 * The internal representation of a (non-default) environment.
 */
typedef struct RTENVINTERNAL
{
    /** Magic value . */
    uint32_t    u32Magic;
    /** Set if this is a record of environment changes, putenv style. */
    bool        fPutEnvBlock;
    /** Set if starting a variable with an equal sign is okay, clear if not okay
     * (RTENV_CREATE_F_ALLOW_EQUAL_FIRST_IN_VAR). */
    bool        fFirstEqual;
    /** Number of variables in the array.
     * This does not include the terminating NULL entry. */
    size_t      cVars;
    /** Capacity (allocated size) of the array.
     * This includes space for the terminating NULL element (for compatibility
     * with the C library), so that c <= cCapacity - 1. */
    size_t      cAllocated;
    /** Array of environment variables.
     * These are always in "NAME=VALUE" form, where the value can be empty. If
     * fPutEnvBlock is set though, there will be "NAME" entries too for variables
     * that need to be removed when merged with another environment block. */
    char      **papszEnv;
    /** Array of environment variables in the process CP.
     * This get (re-)constructed when RTEnvGetExecEnvP method is called. */
    char      **papszEnvOtherCP;

    /** The compare function we're using. */
    DECLCALLBACKMEMBER(int, pfnCompare,(const char *psz1, const char *psz2, size_t cchMax));
} RTENVINTERNAL, *PRTENVINTERNAL;


#ifndef RT_OS_WINDOWS
/**
 * Internal worker that resolves the pointer to the default
 * process environment. (environ)
 *
 * @returns Pointer to the default environment.
 *          This may be NULL.
 */
static const char * const *rtEnvDefault(void)
{
# ifdef RT_OS_DARWIN
    return *(_NSGetEnviron());
# else
    return environ;
# endif
}
#endif


/**
 * Internal worker that creates an environment handle with a specified capacity.
 *
 * @returns IPRT status code.
 * @param   ppIntEnv        Where to store the result.
 * @param   cAllocated      The initial array size.
 * @param   fCaseSensitive  Whether the environment block is case sensitive or
 *                          not.
 * @param   fPutEnvBlock    Indicates whether this is a special environment
 *                          block that will be used to record change another
 *                          block.  We will keep unsets in putenv format, i.e.
 *                          just the variable name without any equal sign.
 * @param   fFirstEqual     The RTENV_CREATE_F_ALLOW_EQUAL_FIRST_IN_VAR value.
                                                      */
static int rtEnvCreate(PRTENVINTERNAL *ppIntEnv, size_t cAllocated, bool fCaseSensitive, bool fPutEnvBlock, bool fFirstEqual)
{
    /*
     * Allocate environment handle.
     */
    PRTENVINTERNAL pIntEnv = (PRTENVINTERNAL)RTMemAlloc(sizeof(*pIntEnv));
    if (pIntEnv)
    {
        /*
         * Pre-allocate the variable array.
         */
        pIntEnv->u32Magic = RTENV_MAGIC;
        pIntEnv->fPutEnvBlock = fPutEnvBlock;
        pIntEnv->fFirstEqual = fFirstEqual;
        pIntEnv->pfnCompare = fCaseSensitive ? RTStrNCmp : RTStrNICmp;
        pIntEnv->papszEnvOtherCP = NULL;
        pIntEnv->cVars = 0;
        pIntEnv->cAllocated = RT_ALIGN_Z(RT_MAX(cAllocated, RTENV_GROW_SIZE), RTENV_GROW_SIZE);
        pIntEnv->papszEnv = (char **)RTMemAllocZ(sizeof(pIntEnv->papszEnv[0]) * pIntEnv->cAllocated);
        if (pIntEnv->papszEnv)
        {
            *ppIntEnv = pIntEnv;
            return VINF_SUCCESS;
        }

        RTMemFree(pIntEnv);
    }

    return VERR_NO_MEMORY;
}


RTDECL(int) RTEnvCreate(PRTENV pEnv)
{
    AssertPtrReturn(pEnv, VERR_INVALID_POINTER);
#ifdef RTENV_ALLOW_EQUAL_FIRST_IN_VAR
    return rtEnvCreate(pEnv, RTENV_GROW_SIZE, true /*fCaseSensitive*/, false /*fPutEnvBlock*/, true /*fFirstEqual*/);
#else
    return rtEnvCreate(pEnv, RTENV_GROW_SIZE, true /*fCaseSensitive*/, false /*fPutEnvBlock*/, false/*fFirstEqual*/);
#endif
}
RT_EXPORT_SYMBOL(RTEnvCreate);


RTDECL(int) RTEnvCreateEx(PRTENV phEnv, uint32_t fFlags)
{
    AssertPtrReturn(phEnv, VERR_INVALID_POINTER);
    AssertReturn(!(fFlags & ~RTENV_CREATE_F_VALID_MASK), VERR_INVALID_FLAGS);
    return rtEnvCreate(phEnv, RTENV_GROW_SIZE, true /*fCaseSensitive*/, false /*fPutEnvBlock*/,
                       RT_BOOL(fFlags & RTENV_CREATE_F_ALLOW_EQUAL_FIRST_IN_VAR));
}
RT_EXPORT_SYMBOL(RTEnvCreateEx);


RTDECL(int) RTEnvDestroy(RTENV Env)
{
    /*
     * Ignore NIL_RTENV and validate input.
     */
    if (    Env == NIL_RTENV
        ||  Env == RTENV_DEFAULT)
        return VINF_SUCCESS;

    PRTENVINTERNAL pIntEnv = Env;
    AssertPtrReturn(pIntEnv, VERR_INVALID_HANDLE);
    AssertReturn(pIntEnv->u32Magic == RTENV_MAGIC, VERR_INVALID_HANDLE);

    /*
     * Do the cleanup.
     */
    RTENV_LOCK(pIntEnv);
    pIntEnv->u32Magic++;
    size_t iVar = pIntEnv->cVars;
    while (iVar-- > 0)
        RTStrFree(pIntEnv->papszEnv[iVar]);
    RTMemFree(pIntEnv->papszEnv);
    pIntEnv->papszEnv = NULL;

    if (pIntEnv->papszEnvOtherCP)
    {
        for (iVar = 0; pIntEnv->papszEnvOtherCP[iVar]; iVar++)
        {
            RTStrFree(pIntEnv->papszEnvOtherCP[iVar]);
            pIntEnv->papszEnvOtherCP[iVar] = NULL;
        }
        RTMemFree(pIntEnv->papszEnvOtherCP);
        pIntEnv->papszEnvOtherCP = NULL;
    }

    RTENV_UNLOCK(pIntEnv);
    /*RTCritSectDelete(&pIntEnv->CritSect) */
    RTMemFree(pIntEnv);

    return VINF_SUCCESS;
}
RT_EXPORT_SYMBOL(RTEnvDestroy);


static int rtEnvCloneDefault(PRTENV phEnv)
{
#ifdef RTENV_ALLOW_EQUAL_FIRST_IN_VAR
    bool const fFirstEqual = true;
#else
    bool const fFirstEqual = false;
#endif

#ifdef RT_OS_WINDOWS
    /*
     * Lock the PEB, get the process environment.
     *
     * On older windows version GetEnviornmentStringsW will not copy the
     * environment block, but return the pointer stored in the PEB.  This
     * should be safer wrt to concurrent changes.
     */
    PPEB pPeb = RTNtCurrentPeb();

    RtlAcquirePebLock();

    /* Count variables in the block: */
    size_t    cVars    = 0;
    PCRTUTF16 pwszzEnv = pPeb->ProcessParameters ? pPeb->ProcessParameters->Environment : NULL;
    if (pwszzEnv)
    {
        PCRTUTF16 pwsz = pwszzEnv;
        while (*pwsz)
        {
            cVars++;
            pwsz += RTUtf16Len(pwsz) + 1;
        }
    }

    PRTENVINTERNAL pIntEnv;
    int rc = rtEnvCreate(&pIntEnv, cVars + 1 /* NULL */, false /*fCaseSensitive*/, false /*fPutEnvBlock*/, fFirstEqual);
    if (RT_SUCCESS(rc))
    {
        size_t iDst;
        for (iDst = 0; iDst < cVars && *pwszzEnv; iDst++, pwszzEnv += RTUtf16Len(pwszzEnv) + 1)
        {
            int rc2 = RTUtf16ToUtf8(pwszzEnv, &pIntEnv->papszEnv[iDst]);
            if (RT_SUCCESS(rc2))
            {
                /* Make sure it contains an '='. */
                if (strchr(pIntEnv->papszEnv[iDst], '='))
                    continue;
                rc2 = RTStrAAppend(&pIntEnv->papszEnv[iDst], "=");
                if (RT_SUCCESS(rc2))
                    continue;
            }

            /* failed fatally. */
            pIntEnv->cVars = iDst + 1;
            RtlReleasePebLock();
            RTEnvDestroy(pIntEnv);
            return rc2;
        }

        Assert(!*pwszzEnv); Assert(iDst == cVars);
        pIntEnv->cVars = iDst;
        pIntEnv->papszEnv[iDst] = NULL;

        /* done */
        *phEnv = pIntEnv;
    }

    RtlReleasePebLock();
    return rc;

#else /* !RT_OS_WINDOWS */

    /*
     * Figure out how many variable to clone.
     */
    const char * const *papszEnv = rtEnvDefault();
    size_t              cVars = 0;
    if (papszEnv)
        while (papszEnv[cVars])
            cVars++;

    bool fCaseSensitive = true;
# if defined(RT_OS_OS2) || defined(RT_OS_WINDOWS)
    /* DOS systems was case insensitive.  A prime example is the 'Path'
       variable on windows which turns into the 'PATH' variable. */
    fCaseSensitive = false;
# endif

    /*
     * Create the duplicate.
     */
    PRTENVINTERNAL pIntEnv;
    int rc = rtEnvCreate(&pIntEnv, cVars + 1 /* NULL */, fCaseSensitive, false /*fPutEnvBlock*/, fFirstEqual);
    if (RT_SUCCESS(rc))
    {
        pIntEnv->cVars = cVars;
        pIntEnv->papszEnv[pIntEnv->cVars] = NULL;

        /* ASSUMES the default environment is in the current codepage. */
        size_t  iDst = 0;
        for (size_t iSrc = 0; iSrc < cVars; iSrc++)
        {
            int rc2 = RTStrCurrentCPToUtf8(&pIntEnv->papszEnv[iDst], papszEnv[iSrc]);
            if (RT_SUCCESS(rc2))
            {
                /* Make sure it contains an '='. */
                iDst++;
                if (strchr(pIntEnv->papszEnv[iDst - 1], '='))
                    continue;
                rc2 = RTStrAAppend(&pIntEnv->papszEnv[iDst - 1], "=");
                if (RT_SUCCESS(rc2))
                    continue;
            }
            else if (rc2 == VERR_NO_TRANSLATION)
            {
                rc = VWRN_ENV_NOT_FULLY_TRANSLATED;
                continue;
            }

            /* failed fatally. */
            pIntEnv->cVars = iDst;
            RTEnvDestroy(pIntEnv);
            return rc2;
        }
        pIntEnv->cVars = iDst;

        /* done */
        *phEnv = pIntEnv;
    }

    return rc;
#endif /* !RT_OS_WINDOWS */
}


/**
 * Clones a non-default environment instance.
 *
 * @param   phEnv           Where to return the handle to the cloned environment.
 * @param   pIntEnvToClone  The source environment. Caller takes care of
 *                          locking.
 */
static int rtEnvCloneNonDefault(PRTENV phEnv, PRTENVINTERNAL pIntEnvToClone)
{
    PRTENVINTERNAL pIntEnv;
    size_t const cVars = pIntEnvToClone->cVars;
    int rc = rtEnvCreate(&pIntEnv, cVars + 1 /* NULL */,
                         pIntEnvToClone->pfnCompare != RTStrNICmp,
                         pIntEnvToClone->fPutEnvBlock,
                         pIntEnvToClone->fFirstEqual);
    if (RT_SUCCESS(rc))
    {
        pIntEnv->cVars = cVars;
        pIntEnv->papszEnv[cVars] = NULL;

        const char * const * const papszEnv = pIntEnvToClone->papszEnv;
        for (size_t iVar = 0; iVar < cVars; iVar++)
        {
            char *pszVar = RTStrDup(papszEnv[iVar]);
            if (RT_UNLIKELY(!pszVar))
            {
                pIntEnv->cVars = iVar;
                RTEnvDestroy(pIntEnv);
                return VERR_NO_STR_MEMORY;
            }
            pIntEnv->papszEnv[iVar] = pszVar;
        }

        /* done */
        *phEnv = pIntEnv;
    }
    return rc;
}


RTDECL(int) RTEnvClone(PRTENV phEnv, RTENV hEnvToClone)
{
    /*
     * Validate input and what kind of source block we're working with.
     */
    int rc;
    AssertPtrReturn(phEnv, VERR_INVALID_POINTER);
    if (hEnvToClone == RTENV_DEFAULT)
        rc = rtEnvCloneDefault(phEnv);
    else
    {
        PRTENVINTERNAL pIntEnvToClone = hEnvToClone;
        AssertPtrReturn(pIntEnvToClone, VERR_INVALID_HANDLE);
        AssertReturn(pIntEnvToClone->u32Magic == RTENV_MAGIC, VERR_INVALID_HANDLE);

        RTENV_LOCK(pIntEnvToClone);
        rc = rtEnvCloneNonDefault(phEnv, pIntEnvToClone);
        RTENV_UNLOCK(pIntEnvToClone);
    }
    return rc;
}
RT_EXPORT_SYMBOL(RTEnvClone);


RTDECL(int) RTEnvCloneUtf16Block(PRTENV phEnv, PCRTUTF16 pwszzBlock, uint32_t fFlags)
{
    AssertPtrReturn(pwszzBlock, VERR_INVALID_POINTER);
    AssertReturn(!fFlags, VERR_INVALID_FLAGS);

    /*
     * Count the number of variables in the block.
     */
    uint32_t  cVars = 0;
    PCRTUTF16 pwsz  = pwszzBlock;
    while (*pwsz != '\0')
    {
        cVars++;
        pwsz += RTUtf16Len(pwsz) + 1;
        AssertReturn(cVars < _256K, VERR_OUT_OF_RANGE);
    }

    /*
     * Create the duplicate.
     */
    PRTENVINTERNAL pIntEnv;
#ifdef RTENV_ALLOW_EQUAL_FIRST_IN_VAR
    int rc = rtEnvCreate(&pIntEnv, cVars + 1 /* NULL */, false /*fCaseSensitive*/, false /*fPutEnvBlock*/, true /*fFirstEqual*/);
#else
    int rc = rtEnvCreate(&pIntEnv, cVars + 1 /* NULL */, false /*fCaseSensitive*/, false /*fPutEnvBlock*/, false /*fFirstEqual*/);
#endif
    if (RT_SUCCESS(rc))
    {
        pIntEnv->cVars = cVars;
        pIntEnv->papszEnv[pIntEnv->cVars] = NULL;

        size_t iDst = 0;
        for (pwsz = pwszzBlock; *pwsz != '\0'; pwsz += RTUtf16Len(pwsz) + 1)
        {
            int rc2 = RTUtf16ToUtf8(pwsz, &pIntEnv->papszEnv[iDst]);
            if (RT_SUCCESS(rc2))
            {
                /* Make sure it contains an '='. */
                const char *pszEqual = strchr(pIntEnv->papszEnv[iDst], '=');
                if (!pszEqual)
                {
                    rc2 = RTStrAAppend(&pIntEnv->papszEnv[iDst], "=");
                    if (RT_SUCCESS(rc2))
                        pszEqual = strchr(pIntEnv->papszEnv[iDst], '=');

                }
                if (pszEqual)
                {
                    /* Check for duplicates, keep the last version. */
                    const char *pchVar        = pIntEnv->papszEnv[iDst];
                    size_t      cchVarNmAndEq = pszEqual - pchVar;
                    for (size_t iDst2 = 0; iDst2 < iDst; iDst2++)
                        if (pIntEnv->pfnCompare(pIntEnv->papszEnv[iDst2], pchVar, cchVarNmAndEq) == 0)
                        {
                            RTStrFree(pIntEnv->papszEnv[iDst2]);
                            pIntEnv->papszEnv[iDst2] = pIntEnv->papszEnv[iDst];
                            pIntEnv->papszEnv[iDst]  = NULL;
                            iDst--;
                            break;
                        }
                    iDst++;
                    continue;
                }
                iDst++;
            }

            /* failed fatally. */
            pIntEnv->cVars = iDst;
            RTEnvDestroy(pIntEnv);
            return rc2;
        }
        Assert(iDst <= pIntEnv->cVars);
        pIntEnv->cVars = iDst;

        /* done */
        *phEnv = pIntEnv;
    }
    return rc;
}
RT_EXPORT_SYMBOL(RTEnvCloneUtf16Block);



RTDECL(int) RTEnvReset(RTENV hEnv)
{
    PRTENVINTERNAL pIntEnv = hEnv;
    AssertPtrReturn(pIntEnv, VERR_INVALID_HANDLE);
    AssertReturn(pIntEnv->u32Magic == RTENV_MAGIC, VERR_INVALID_HANDLE);

    RTENV_LOCK(pIntEnv);

    size_t iVar = pIntEnv->cVars;
    pIntEnv->cVars = 0;
    while (iVar-- > 0)
    {
        RTMemFree(pIntEnv->papszEnv[iVar]);
        pIntEnv->papszEnv[iVar] = NULL;
    }

    RTENV_UNLOCK(pIntEnv);
    return VINF_SUCCESS;
}
RT_EXPORT_SYMBOL(RTEnvReset);


/**
 * Appends an already allocated string to papszEnv.
 *
 * @returns IPRT status code
 * @param   pIntEnv             The environment block to append it to.
 * @param   pszEntry            The string to add.  Already duplicated, caller
 *                              does error cleanup.
 */
static int rtEnvIntAppend(PRTENVINTERNAL pIntEnv, char *pszEntry)
{
    /*
     * Do we need to resize the array?
     */
    int rc = VINF_SUCCESS;
    size_t iVar = pIntEnv->cVars;
    if (iVar + 2 > pIntEnv->cAllocated)
    {
        void *pvNew = RTMemRealloc(pIntEnv->papszEnv, sizeof(char *) * (pIntEnv->cAllocated + RTENV_GROW_SIZE));
        if (!pvNew)
            rc = VERR_NO_MEMORY;
        else
        {
            pIntEnv->papszEnv = (char **)pvNew;
            pIntEnv->cAllocated += RTENV_GROW_SIZE;
            for (size_t iNewVar = pIntEnv->cVars; iNewVar < pIntEnv->cAllocated; iNewVar++)
                pIntEnv->papszEnv[iNewVar] = NULL;
        }
    }
    if (RT_SUCCESS(rc))
    {
        /*
         * Append it.
         */
        pIntEnv->papszEnv[iVar] = pszEntry;
        pIntEnv->papszEnv[iVar + 1] = NULL; /* this isn't really necessary, but doesn't hurt. */
        pIntEnv->cVars = iVar + 1;
    }
    return rc;
}


/**
 * Worker for RTEnvSetEx and RTEnvPutEx.
 */
static int rtEnvSetExWorker(RTENV Env, const char *pchVar, size_t cchVar, const char *pszValue)
{
    int rc;
    if (Env == RTENV_DEFAULT)
    {
#ifdef RT_OS_WINDOWS
        extern int rtEnvSetUtf8Worker(const char *pchVar, size_t cchVar, const char *pszValue);
        rc = rtEnvSetUtf8Worker(pchVar, cchVar, pszValue);
#else
        /*
         * Since RTEnvPut isn't UTF-8 clean and actually expects the strings
         * to be in the current code page (codeset), we'll do the necessary
         * conversions here.
         */
        char *pszVarOtherCP;
        rc = RTStrUtf8ToCurrentCPEx(&pszVarOtherCP, pchVar, cchVar);
        if (RT_SUCCESS(rc))
        {
            char *pszValueOtherCP;
            rc = RTStrUtf8ToCurrentCP(&pszValueOtherCP, pszValue);
            if (RT_SUCCESS(rc))
            {
                rc = RTEnvSet(pszVarOtherCP, pszValueOtherCP);
                RTStrFree(pszValueOtherCP);
            }
            RTStrFree(pszVarOtherCP);
        }
#endif
    }
    else
    {
        PRTENVINTERNAL pIntEnv = Env;
        AssertPtrReturn(pIntEnv, VERR_INVALID_HANDLE);
        AssertReturn(pIntEnv->u32Magic == RTENV_MAGIC, VERR_INVALID_HANDLE);

        /*
         * Create the variable string.
         */
        const size_t cchValue = strlen(pszValue);
        char *pszEntry = (char *)RTMemAlloc(cchVar + cchValue + 2);
        if (pszEntry)
        {
            memcpy(pszEntry, pchVar, cchVar);
            pszEntry[cchVar] = '=';
            memcpy(&pszEntry[cchVar + 1], pszValue, cchValue + 1);

            RTENV_LOCK(pIntEnv);

            /*
             * Find the location of the variable. (iVar = cVars if new)
             */
            rc = VINF_SUCCESS;
            size_t iVar;
            for (iVar = 0; iVar < pIntEnv->cVars; iVar++)
                if (    !pIntEnv->pfnCompare(pIntEnv->papszEnv[iVar], pchVar, cchVar)
                    &&  (   pIntEnv->papszEnv[iVar][cchVar] == '='
                         || pIntEnv->papszEnv[iVar][cchVar] == '\0') )
                    break;
            if (iVar < pIntEnv->cVars)
            {
                /*
                 * Replace the current entry. Simple.
                 */
                RTMemFree(pIntEnv->papszEnv[iVar]);
                pIntEnv->papszEnv[iVar] = pszEntry;
            }
            else
            {
                /*
                 * New variable, append it.
                 */
                Assert(pIntEnv->cVars == iVar);
                rc = rtEnvIntAppend(pIntEnv, pszEntry);
            }

            RTENV_UNLOCK(pIntEnv);

            if (RT_FAILURE(rc))
                RTMemFree(pszEntry);
        }
        else
            rc = VERR_NO_MEMORY;
    }
    return rc;
}


RTDECL(int) RTEnvSetEx(RTENV Env, const char *pszVar, const char *pszValue)
{
    AssertPtrReturn(pszVar, VERR_INVALID_POINTER);
    AssertPtrReturn(pszValue, VERR_INVALID_POINTER);
    size_t const cchVar = strlen(pszVar);
    AssertReturn(cchVar > 0, VERR_ENV_INVALID_VAR_NAME);
    char const *pszEq = (char const *)memchr(pszVar, '=', cchVar);
    if (!pszEq)
    { /* likely */ }
    else
    {
        AssertReturn(Env != RTENV_DEFAULT, VERR_ENV_INVALID_VAR_NAME);
        PRTENVINTERNAL pIntEnv = Env;
        AssertPtrReturn(pIntEnv, VERR_INVALID_HANDLE);
        AssertReturn(pIntEnv->u32Magic == RTENV_MAGIC, VERR_INVALID_HANDLE);
        AssertReturn(pIntEnv->fFirstEqual, VERR_ENV_INVALID_VAR_NAME);
        AssertReturn(memchr(pszVar + 1, '=', cchVar - 1) == NULL, VERR_ENV_INVALID_VAR_NAME);
    }

    return rtEnvSetExWorker(Env, pszVar, cchVar, pszValue);
}
RT_EXPORT_SYMBOL(RTEnvSetEx);


RTDECL(int) RTEnvUnsetEx(RTENV Env, const char *pszVar)
{
    AssertPtrReturn(pszVar, VERR_INVALID_POINTER);
    AssertReturn(*pszVar, VERR_ENV_INVALID_VAR_NAME);

    int rc;
    if (Env == RTENV_DEFAULT)
    {
#ifdef RTENV_IMPLEMENTS_UTF8_DEFAULT_ENV_API
        rc = RTEnvUnsetUtf8(pszVar);
#else
        /*
         * Since RTEnvUnset isn't UTF-8 clean and actually expects the strings
         * to be in the current code page (codeset), we'll do the necessary
         * conversions here.
         */
        char *pszVarOtherCP;
        rc = RTStrUtf8ToCurrentCP(&pszVarOtherCP, pszVar);
        if (RT_SUCCESS(rc))
        {
            rc = RTEnvUnset(pszVarOtherCP);
            RTStrFree(pszVarOtherCP);
        }
#endif
    }
    else
    {
        PRTENVINTERNAL pIntEnv = Env;
        AssertPtrReturn(pIntEnv, VERR_INVALID_HANDLE);
        AssertReturn(pIntEnv->u32Magic == RTENV_MAGIC, VERR_INVALID_HANDLE);
        const size_t cchVar = strlen(pszVar);
        AssertReturn(cchVar > 0, VERR_ENV_INVALID_VAR_NAME);
        AssertReturn(strchr(pIntEnv->fFirstEqual ? pszVar + 1 : pszVar, '=') == NULL, VERR_ENV_INVALID_VAR_NAME);

        RTENV_LOCK(pIntEnv);

        /*
         * Remove all variable by the given name.
         */
        rc = VINF_ENV_VAR_NOT_FOUND;
        size_t iVar;
        for (iVar = 0; iVar < pIntEnv->cVars; iVar++)
            if (    !pIntEnv->pfnCompare(pIntEnv->papszEnv[iVar], pszVar, cchVar)
                &&  (   pIntEnv->papszEnv[iVar][cchVar] == '='
                     || pIntEnv->papszEnv[iVar][cchVar] == '\0') )
            {
                if (!pIntEnv->fPutEnvBlock)
                {
                    RTMemFree(pIntEnv->papszEnv[iVar]);
                    pIntEnv->cVars--;
                    if (pIntEnv->cVars > 0)
                        pIntEnv->papszEnv[iVar] = pIntEnv->papszEnv[pIntEnv->cVars];
                    pIntEnv->papszEnv[pIntEnv->cVars] = NULL;
                }
                else
                {
                    /* Record this unset by keeping the variable without any equal sign. */
                    pIntEnv->papszEnv[iVar][cchVar] = '\0';
                }
                rc = VINF_SUCCESS;
                /* no break, there could be more. */
            }

        /*
         * If this is a change record, we may need to add it.
         */
        if (rc == VINF_ENV_VAR_NOT_FOUND && pIntEnv->fPutEnvBlock)
        {
            char *pszEntry = (char *)RTMemDup(pszVar, cchVar + 1);
            if (pszEntry)
            {
                rc = rtEnvIntAppend(pIntEnv, pszEntry);
                if (RT_SUCCESS(rc))
                    rc = VINF_ENV_VAR_NOT_FOUND;
                else
                    RTMemFree(pszEntry);
            }
            else
                rc = VERR_NO_MEMORY;
        }

        RTENV_UNLOCK(pIntEnv);
    }
    return rc;

}
RT_EXPORT_SYMBOL(RTEnvUnsetEx);


RTDECL(int) RTEnvPutEx(RTENV Env, const char *pszVarEqualValue)
{
    int rc;
    AssertPtrReturn(pszVarEqualValue, VERR_INVALID_POINTER);
    const char *pszEq = strchr(pszVarEqualValue, '=');
    if (   pszEq == pszVarEqualValue
        && Env != RTENV_DEFAULT)
    {
        PRTENVINTERNAL pIntEnv = Env;
        AssertPtrReturn(pIntEnv, VERR_INVALID_HANDLE);
        AssertReturn(pIntEnv->u32Magic == RTENV_MAGIC, VERR_INVALID_HANDLE);
        if (pIntEnv->fFirstEqual)
            pszEq = strchr(pszVarEqualValue + 1, '=');
    }
    if (!pszEq)
        rc = RTEnvUnsetEx(Env, pszVarEqualValue);
    else
    {
        AssertReturn(pszEq != pszVarEqualValue, VERR_ENV_INVALID_VAR_NAME);
        rc = rtEnvSetExWorker(Env, pszVarEqualValue, pszEq - pszVarEqualValue, pszEq + 1);
    }
    return rc;
}
RT_EXPORT_SYMBOL(RTEnvPutEx);


RTDECL(int) RTEnvGetEx(RTENV Env, const char *pszVar, char *pszValue, size_t cbValue, size_t *pcchActual)
{
    AssertPtrReturn(pszVar, VERR_INVALID_POINTER);
    AssertPtrNullReturn(pszValue, VERR_INVALID_POINTER);
    AssertPtrNullReturn(pcchActual, VERR_INVALID_POINTER);
    AssertReturn(pcchActual || (pszValue && cbValue), VERR_INVALID_PARAMETER);

    if (pcchActual)
        *pcchActual = 0;
    int rc;
    if (Env == RTENV_DEFAULT)
    {
#ifdef RTENV_IMPLEMENTS_UTF8_DEFAULT_ENV_API
        rc = RTEnvGetUtf8(pszVar, pszValue, cbValue, pcchActual);
#else
        /*
         * Since RTEnvGet isn't UTF-8 clean and actually expects the strings
         * to be in the current code page (codeset), we'll do the necessary
         * conversions here.
         */
        char *pszVarOtherCP;
        rc = RTStrUtf8ToCurrentCP(&pszVarOtherCP, pszVar);
        if (RT_SUCCESS(rc))
        {
            const char *pszValueOtherCP = RTEnvGet(pszVarOtherCP);
            RTStrFree(pszVarOtherCP);
            if (pszValueOtherCP)
            {
                char *pszValueUtf8;
                rc = RTStrCurrentCPToUtf8(&pszValueUtf8, pszValueOtherCP);
                if (RT_SUCCESS(rc))
                {
                    rc = VINF_SUCCESS;
                    size_t cch = strlen(pszValueUtf8);
                    if (pcchActual)
                        *pcchActual = cch;
                    if (pszValue && cbValue)
                    {
                        if (cch < cbValue)
                            memcpy(pszValue, pszValueUtf8, cch + 1);
                        else
                            rc = VERR_BUFFER_OVERFLOW;
                    }
                    RTStrFree(pszValueUtf8);
                }
            }
            else
                rc = VERR_ENV_VAR_NOT_FOUND;
        }
#endif
    }
    else
    {
        PRTENVINTERNAL pIntEnv = Env;
        AssertPtrReturn(pIntEnv, VERR_INVALID_HANDLE);
        AssertReturn(pIntEnv->u32Magic == RTENV_MAGIC, VERR_INVALID_HANDLE);
        const size_t cchVar = strlen(pszVar);
        AssertReturn(cchVar > 0, VERR_ENV_INVALID_VAR_NAME);
        AssertReturn(strchr(pIntEnv->fFirstEqual ? pszVar + 1 : pszVar, '=') == NULL, VERR_ENV_INVALID_VAR_NAME);

        RTENV_LOCK(pIntEnv);

        /*
         * Locate the first variable and return it to the caller.
         */
        rc = VERR_ENV_VAR_NOT_FOUND;
        size_t iVar;
        for (iVar = 0; iVar < pIntEnv->cVars; iVar++)
            if (!pIntEnv->pfnCompare(pIntEnv->papszEnv[iVar], pszVar, cchVar))
            {
                if (pIntEnv->papszEnv[iVar][cchVar] == '=')
                {
                    rc = VINF_SUCCESS;
                    const char *pszValueOrg = pIntEnv->papszEnv[iVar] + cchVar + 1;
                    size_t cch = strlen(pszValueOrg);
                    if (pcchActual)
                        *pcchActual = cch;
                    if (pszValue && cbValue)
                    {
                        if (cch < cbValue)
                            memcpy(pszValue, pszValueOrg, cch + 1);
                        else
                            rc = VERR_BUFFER_OVERFLOW;
                    }
                    break;
                }
                if (pIntEnv->papszEnv[iVar][cchVar] == '\0')
                {
                    Assert(pIntEnv->fPutEnvBlock);
                    rc = VERR_ENV_VAR_UNSET;
                    break;
                }
            }

        RTENV_UNLOCK(pIntEnv);
    }
    return rc;
}
RT_EXPORT_SYMBOL(RTEnvGetEx);


RTDECL(bool) RTEnvExistEx(RTENV Env, const char *pszVar)
{
    AssertPtrReturn(pszVar, false);

    bool fExists = false;
    if (Env == RTENV_DEFAULT)
    {
#ifdef RTENV_IMPLEMENTS_UTF8_DEFAULT_ENV_API
        fExists = RTEnvExistsUtf8(pszVar);
#else
        /*
         * Since RTEnvExist isn't UTF-8 clean and actually expects the strings
         * to be in the current code page (codeset), we'll do the necessary
         * conversions here.
         */
        char *pszVarOtherCP;
        int rc = RTStrUtf8ToCurrentCP(&pszVarOtherCP, pszVar);
        if (RT_SUCCESS(rc))
        {
            fExists = RTEnvExist(pszVarOtherCP);
            RTStrFree(pszVarOtherCP);
        }
#endif
    }
    else
    {
        PRTENVINTERNAL pIntEnv = Env;
        AssertPtrReturn(pIntEnv, false);
        AssertReturn(pIntEnv->u32Magic == RTENV_MAGIC, false);
        const size_t cchVar = strlen(pszVar);
        AssertReturn(cchVar > 0, false);
        AssertReturn(strchr(pIntEnv->fFirstEqual ? pszVar + 1 : pszVar, '=') == NULL, false);

        RTENV_LOCK(pIntEnv);

        /*
         * Simple search.
         */
        for (size_t iVar = 0; iVar < pIntEnv->cVars; iVar++)
            if (!pIntEnv->pfnCompare(pIntEnv->papszEnv[iVar], pszVar, cchVar))
            {
                if (pIntEnv->papszEnv[iVar][cchVar] == '=')
                {
                    fExists = true;
                    break;
                }
                if (pIntEnv->papszEnv[iVar][cchVar] == '\0')
                    break;
            }

        RTENV_UNLOCK(pIntEnv);
    }
    return fExists;
}
RT_EXPORT_SYMBOL(RTEnvExistEx);


#ifndef RT_OS_WINDOWS
RTDECL(char const * const *) RTEnvGetExecEnvP(RTENV Env)
{
    const char * const *papszRet;
    if (Env == RTENV_DEFAULT)
    {
        /** @todo fix this API it's fundamentally wrong! */
        papszRet = rtEnvDefault();
        if (!papszRet)
        {
            static const char * const s_papszDummy[2] = { NULL, NULL };
            papszRet = &s_papszDummy[0];
        }
    }
    else
    {
        PRTENVINTERNAL pIntEnv = Env;
        AssertPtrReturn(pIntEnv, NULL);
        AssertReturn(pIntEnv->u32Magic == RTENV_MAGIC, NULL);

        RTENV_LOCK(pIntEnv);

        /*
         * Free any old envp.
         */
        if (pIntEnv->papszEnvOtherCP)
        {
            for (size_t iVar = 0; pIntEnv->papszEnvOtherCP[iVar]; iVar++)
            {
                RTStrFree(pIntEnv->papszEnvOtherCP[iVar]);
                pIntEnv->papszEnvOtherCP[iVar] = NULL;
            }
            RTMemFree(pIntEnv->papszEnvOtherCP);
            pIntEnv->papszEnvOtherCP = NULL;
        }

        /*
         * Construct a new envp with the strings in the process code set.
         */
        char **papsz;
        papszRet = pIntEnv->papszEnvOtherCP = papsz = (char **)RTMemAlloc(sizeof(char *) * (pIntEnv->cVars + 1));
        if (papsz)
        {
            papsz[pIntEnv->cVars] = NULL;
            for (size_t iVar = 0; iVar < pIntEnv->cVars; iVar++)
            {
                int rc = RTStrUtf8ToCurrentCP(&papsz[iVar], pIntEnv->papszEnv[iVar]);
                if (RT_FAILURE(rc))
                {
                    /* RTEnvDestroy / we cleans up later. */
                    papsz[iVar] = NULL;
                    AssertRC(rc);
                    papszRet = NULL;
                    break;
                }
            }
        }

        RTENV_UNLOCK(pIntEnv);
    }
    return papszRet;
}
RT_EXPORT_SYMBOL(RTEnvGetExecEnvP);
#endif /* !RT_OS_WINDOWS */


/**
 * RTSort callback for comparing two environment variables.
 *
 * @returns -1, 0, 1. See PFNRTSORTCMP.
 * @param   pvElement1          Variable 1.
 * @param   pvElement2          Variable 2.
 * @param   pvUser              Ignored.
 */
static DECLCALLBACK(int) rtEnvSortCompare(const void *pvElement1, const void *pvElement2, void *pvUser)
{
    NOREF(pvUser);
    int iDiff = strcmp((const char *)pvElement1, (const char *)pvElement2);
    if (iDiff < 0)
        iDiff = -1;
    else if (iDiff > 0)
        iDiff = 1;
    return iDiff;
}


RTDECL(int) RTEnvQueryUtf16Block(RTENV hEnv, PRTUTF16 *ppwszzBlock)
{
    RTENV           hClone  = NIL_RTENV;
    PRTENVINTERNAL  pIntEnv;
    int             rc;

    /*
     * Validate / simplify input.
     */
    if (hEnv == RTENV_DEFAULT)
    {
        rc = RTEnvClone(&hClone, RTENV_DEFAULT);
        if (RT_FAILURE(rc))
            return rc;
        pIntEnv = hClone;
    }
    else
    {
        pIntEnv = hEnv;
        AssertPtrReturn(pIntEnv, VERR_INVALID_HANDLE);
        AssertReturn(pIntEnv->u32Magic == RTENV_MAGIC, VERR_INVALID_HANDLE);
        rc = VINF_SUCCESS;
    }

    RTENV_LOCK(pIntEnv);

    /*
     * Sort it first.
     */
    RTSortApvShell((void **)pIntEnv->papszEnv, pIntEnv->cVars, rtEnvSortCompare, pIntEnv);

    /*
     * Calculate the size.
     */
    size_t cwc;
    size_t cwcTotal = 2;
    for (size_t iVar = 0; iVar < pIntEnv->cVars; iVar++)
    {
        rc = RTStrCalcUtf16LenEx(pIntEnv->papszEnv[iVar], RTSTR_MAX, &cwc);
        AssertRCBreak(rc);
        cwcTotal += cwc + 1;
    }

    PRTUTF16 pwszzBlock = NULL;
    if (RT_SUCCESS(rc))
    {
        /*
         * Perform the conversion.
         */
        PRTUTF16 pwszz = pwszzBlock = (PRTUTF16)RTMemAlloc(cwcTotal * sizeof(RTUTF16));
        if (pwszz)
        {
            size_t cwcLeft = cwcTotal;
            for (size_t iVar = 0; iVar < pIntEnv->cVars; iVar++)
            {
                rc = RTStrToUtf16Ex(pIntEnv->papszEnv[iVar], RTSTR_MAX,
                                    &pwszz, cwcTotal - (pwszz - pwszzBlock), &cwc);
                AssertRCBreak(rc);
                pwszz   += cwc + 1;
                cwcLeft -= cwc + 1;
                AssertBreakStmt(cwcLeft >= 2, rc = VERR_INTERNAL_ERROR_3);
            }
            AssertStmt(cwcLeft == 2 || RT_FAILURE(rc), rc = VERR_INTERNAL_ERROR_2);
            if (RT_SUCCESS(rc))
            {
                pwszz[0] = '\0';
                pwszz[1] = '\0';
            }
            else
            {
                RTMemFree(pwszzBlock);
                pwszzBlock = NULL;
            }
        }
        else
            rc = VERR_NO_MEMORY;
    }

    RTENV_UNLOCK(pIntEnv);

    if (hClone != NIL_RTENV)
        RTEnvDestroy(hClone);
    if (RT_SUCCESS(rc))
        *ppwszzBlock = pwszzBlock;
    return rc;
}
RT_EXPORT_SYMBOL(RTEnvQueryUtf16Block);


RTDECL(void) RTEnvFreeUtf16Block(PRTUTF16 pwszzBlock)
{
    RTMemFree(pwszzBlock);
}
RT_EXPORT_SYMBOL(RTEnvFreeUtf16Block);


RTDECL(int) RTEnvQueryUtf8Block(RTENV hEnv, bool fSorted, char **ppszzBlock, size_t *pcbBlock)
{
    RTENV           hClone  = NIL_RTENV;
    PRTENVINTERNAL  pIntEnv;
    int             rc;

    /*
     * Validate / simplify input.
     */
    if (hEnv == RTENV_DEFAULT)
    {
        rc = RTEnvClone(&hClone, RTENV_DEFAULT);
        if (RT_FAILURE(rc))
            return rc;
        pIntEnv = hClone;
    }
    else
    {
        pIntEnv = hEnv;
        AssertPtrReturn(pIntEnv, VERR_INVALID_HANDLE);
        AssertReturn(pIntEnv->u32Magic == RTENV_MAGIC, VERR_INVALID_HANDLE);
        rc = VINF_SUCCESS;
    }

    RTENV_LOCK(pIntEnv);

    /*
     * Sort it, if requested.
     */
    if (fSorted)
        RTSortApvShell((void **)pIntEnv->papszEnv, pIntEnv->cVars, rtEnvSortCompare, pIntEnv);

    /*
     * Calculate the size. We add one extra terminator just to be on the safe side.
     */
    size_t cbBlock = 2;
    for (size_t iVar = 0; iVar < pIntEnv->cVars; iVar++)
        cbBlock += strlen(pIntEnv->papszEnv[iVar]) + 1;

    if (pcbBlock)
        *pcbBlock = cbBlock - 1;

    /*
     * Allocate memory and copy out the variables.
     */
    char *pszzBlock;
    char *pszz = pszzBlock = (char *)RTMemAlloc(cbBlock);
    if (pszz)
    {
        size_t cbLeft = cbBlock;
        for (size_t iVar = 0; iVar < pIntEnv->cVars; iVar++)
        {
            size_t cb = strlen(pIntEnv->papszEnv[iVar]) + 1;
            AssertBreakStmt(cb + 2 <= cbLeft, rc = VERR_INTERNAL_ERROR_3);
            memcpy(pszz, pIntEnv->papszEnv[iVar], cb);
            pszz   += cb;
            cbLeft -= cb;
        }
        if (RT_SUCCESS(rc))
        {
            pszz[0] = '\0';
            pszz[1] = '\0'; /* The extra one. */
        }
        else
        {
            RTMemFree(pszzBlock);
            pszzBlock = NULL;
        }
    }
    else
        rc = VERR_NO_MEMORY;

    RTENV_UNLOCK(pIntEnv);

    if (hClone != NIL_RTENV)
        RTEnvDestroy(hClone);
    if (RT_SUCCESS(rc))
        *ppszzBlock = pszzBlock;
    return rc;
}
RT_EXPORT_SYMBOL(RTEnvQueryUtf8Block);


RTDECL(void) RTEnvFreeUtf8Block(char *pszzBlock)
{
    RTMemFree(pszzBlock);
}
RT_EXPORT_SYMBOL(RTEnvFreeUtf8Block);


RTDECL(uint32_t) RTEnvCountEx(RTENV hEnv)
{
    PRTENVINTERNAL pIntEnv = hEnv;
    AssertPtrReturn(pIntEnv, UINT32_MAX);
    AssertReturn(pIntEnv->u32Magic == RTENV_MAGIC, UINT32_MAX);

    RTENV_LOCK(pIntEnv);
    uint32_t cVars = (uint32_t)pIntEnv->cVars;
    RTENV_UNLOCK(pIntEnv);

    return cVars;
}
RT_EXPORT_SYMBOL(RTEnvCountEx);


RTDECL(int) RTEnvGetByIndexEx(RTENV hEnv, uint32_t iVar, char *pszVar, size_t cbVar, char *pszValue, size_t cbValue)
{
    PRTENVINTERNAL pIntEnv = hEnv;
    AssertPtrReturn(pIntEnv, UINT32_MAX);
    AssertReturn(pIntEnv->u32Magic == RTENV_MAGIC, UINT32_MAX);
    if (cbVar)
        AssertPtrReturn(pszVar, VERR_INVALID_POINTER);
    if (cbValue)
        AssertPtrReturn(pszValue, VERR_INVALID_POINTER);

    RTENV_LOCK(pIntEnv);

    int rc;
    if (iVar < pIntEnv->cVars)
    {
        const char *pszSrcVar   = pIntEnv->papszEnv[iVar];
        const char *pszSrcValue = strchr(pszSrcVar, '=');
        if (pszSrcValue == pszSrcVar && pIntEnv->fFirstEqual)
            pszSrcValue = strchr(pszSrcVar + 1, '=');
        bool        fHasEqual   = pszSrcValue != NULL;
        if (pszSrcValue)
        {
            pszSrcValue++;
            rc = VINF_SUCCESS;
        }
        else
        {
            pszSrcValue = strchr(pszSrcVar, '\0');
            rc = VINF_ENV_VAR_UNSET;
        }
        if (cbVar)
        {
            int rc2 = RTStrCopyEx(pszVar, cbVar, pszSrcVar, pszSrcValue - pszSrcVar - fHasEqual);
            if (RT_FAILURE(rc2))
                rc = rc2;
        }
        if (cbValue)
        {
            int rc2 = RTStrCopy(pszValue, cbValue, pszSrcValue);
            if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
                rc = rc2;
        }
    }
    else
        rc = VERR_ENV_VAR_NOT_FOUND;

    RTENV_UNLOCK(pIntEnv);

    return rc;
}
RT_EXPORT_SYMBOL(RTEnvGetByIndexEx);


RTDECL(const char *) RTEnvGetByIndexRawEx(RTENV hEnv, uint32_t iVar)
{
    PRTENVINTERNAL pIntEnv = hEnv;
    AssertPtrReturn(pIntEnv, NULL);
    AssertReturn(pIntEnv->u32Magic == RTENV_MAGIC, NULL);

    RTENV_LOCK(pIntEnv);

    const char *pszRet;
    if (iVar < pIntEnv->cVars)
        pszRet = pIntEnv->papszEnv[iVar];
    else
        pszRet = NULL;

    RTENV_UNLOCK(pIntEnv);

    return pszRet;
}
RT_EXPORT_SYMBOL(RTEnvGetByIndexRawEx);


RTDECL(int) RTEnvCreateChangeRecord(PRTENV phEnv)
{
    AssertPtrReturn(phEnv, VERR_INVALID_POINTER);
#ifdef RTENV_ALLOW_EQUAL_FIRST_IN_VAR
    return rtEnvCreate(phEnv, RTENV_GROW_SIZE, true /*fCaseSensitive*/, true /*fPutEnvBlock*/, true /*fFirstEqual*/);
#else
    return rtEnvCreate(phEnv, RTENV_GROW_SIZE, true /*fCaseSensitive*/, true /*fPutEnvBlock*/, false /*fFirstEqual*/);
#endif
}
RT_EXPORT_SYMBOL(RTEnvCreateChangeRecord);


RTDECL(int) RTEnvCreateChangeRecordEx(PRTENV phEnv, uint32_t fFlags)
{
    AssertPtrReturn(phEnv, VERR_INVALID_POINTER);
    AssertReturn(!(fFlags & ~RTENV_CREATE_F_VALID_MASK), VERR_INVALID_FLAGS);
    return rtEnvCreate(phEnv, RTENV_GROW_SIZE, true /*fCaseSensitive*/, true /*fPutEnvBlock*/,
                       RT_BOOL(fFlags & RTENV_CREATE_F_ALLOW_EQUAL_FIRST_IN_VAR));
}
RT_EXPORT_SYMBOL(RTEnvCreateChangeRecord);


RTDECL(bool) RTEnvIsChangeRecord(RTENV hEnv)
{
    if (hEnv == RTENV_DEFAULT)
        return false;

    PRTENVINTERNAL pIntEnv = hEnv;
    AssertPtrReturn(pIntEnv, false);
    AssertReturn(pIntEnv->u32Magic == RTENV_MAGIC, false);
    return pIntEnv->fPutEnvBlock;
}
RT_EXPORT_SYMBOL(RTEnvIsChangeRecord);


RTDECL(int) RTEnvApplyChanges(RTENV hEnvDst, RTENV hEnvChanges)
{
    PRTENVINTERNAL pIntEnvChanges = hEnvChanges;
    AssertPtrReturn(pIntEnvChanges, VERR_INVALID_HANDLE);
    AssertReturn(pIntEnvChanges->u32Magic == RTENV_MAGIC, VERR_INVALID_HANDLE);

    /** @todo lock validator trouble ahead here! */
    RTENV_LOCK(pIntEnvChanges);

    int rc = VINF_SUCCESS;
    for (uint32_t iChange = 0; iChange < pIntEnvChanges->cVars && RT_SUCCESS(rc); iChange++)
        rc = RTEnvPutEx(hEnvDst, pIntEnvChanges->papszEnv[iChange]);

    RTENV_UNLOCK(pIntEnvChanges);

    return rc;
}
RT_EXPORT_SYMBOL(RTEnvApplyChanges);

