static void tstRTCreateProcEx5(const char *pszUser, const char *pszPassword)
{
    RTTestISubF("As user \"%s\" with password \"%s\"", pszUser, pszPassword);

    const char * apszArgs[3] =
    {
        "test", /* user name */
        "--testcase-child-5",
        NULL
    };

    RTPROCESS hProc;

    /* Test for invalid logons. */
    RTTESTI_CHECK_RC_RETV(RTProcCreateEx(g_szExecName, apszArgs, RTENV_DEFAULT, 0 /*fFlags*/, NULL,
                                         NULL, NULL, "non-existing-user", "wrong-password", &hProc), VERR_AUTHENTICATION_FAILURE);
    /* Test for invalid application. */
    RTTESTI_CHECK_RC_RETV(RTProcCreateEx("non-existing-app", apszArgs, RTENV_DEFAULT, 0 /*fFlags*/, NULL,
                                         NULL, NULL, NULL, NULL, &hProc), VERR_FILE_NOT_FOUND);
    /* Test a (hopefully) valid user/password logon (given by parameters of this function). */
    RTTESTI_CHECK_RC_RETV(RTProcCreateEx(g_szExecName, apszArgs, RTENV_DEFAULT, 0 /*fFlags*/, NULL,
                                         NULL, NULL, pszUser, pszPassword, &hProc), VINF_SUCCESS);
    RTPROCSTATUS ProcStatus = { -1, RTPROCEXITREASON_ABEND };
    RTTESTI_CHECK_RC(RTProcWait(hProc, RTPROCWAIT_FLAGS_BLOCK, &ProcStatus), VINF_SUCCESS);

    if (ProcStatus.enmReason != RTPROCEXITREASON_NORMAL || ProcStatus.iStatus != 0)
        RTTestIFailed("enmReason=%d iStatus=%d", ProcStatus.enmReason, ProcStatus.iStatus);
    else
        RTTestIPassed(NULL);
}
Ejemplo n.º 2
0
RTR3DECL(int)   RTProcCreate(const char *pszExec, const char * const *papszArgs, RTENV Env, unsigned fFlags, PRTPROCESS pProcess)
{
    return RTProcCreateEx(pszExec, papszArgs, Env, fFlags,
                          NULL, NULL, NULL,  /* standard handles */
                          NULL /*pszAsUser*/, NULL /* pszPassword*/,
                          pProcess);
}
static void tstRTCreateProcEx2(const char *pszAsUser, const char *pszPassword)
{
    RTTestISub("Standard Err");

    RTPIPE hPipeR, hPipeW;
    RTTESTI_CHECK_RC_RETV(RTPipeCreate(&hPipeR, &hPipeW, RTPIPE_C_INHERIT_WRITE), VINF_SUCCESS);
    const char * apszArgs[3] =
    {
        "non-existing-non-executable-file",
        "--testcase-child-2",
        NULL
    };
    RTHANDLE Handle;
    Handle.enmType = RTHANDLETYPE_PIPE;
    Handle.u.hPipe = hPipeW;
    RTPROCESS hProc;
    RTTESTI_CHECK_RC_RETV(RTProcCreateEx(g_szExecName, apszArgs, RTENV_DEFAULT, 0 /*fFlags*/, NULL,
                                         NULL, &Handle, pszAsUser, pszPassword, &hProc), VINF_SUCCESS);
    RTTESTI_CHECK_RC(RTPipeClose(hPipeW), VINF_SUCCESS);

    char    szOutput[_4K];
    size_t  offOutput = 0;
    for (;;)
    {
        size_t cbLeft = sizeof(szOutput) - 1 - offOutput;
        RTTESTI_CHECK(cbLeft > 0);
        if (cbLeft == 0)
            break;

        size_t cbRead;
        int rc = RTPipeReadBlocking(hPipeR, &szOutput[offOutput], cbLeft, &cbRead);
        if (RT_FAILURE(rc))
        {
            RTTESTI_CHECK_RC(rc, VERR_BROKEN_PIPE);
            break;
        }
        offOutput += cbRead;
    }
    szOutput[offOutput] = '\0';
    RTTESTI_CHECK_RC(RTPipeClose(hPipeR), VINF_SUCCESS);

    RTPROCSTATUS ProcStatus = { -1, RTPROCEXITREASON_ABEND };
    RTTESTI_CHECK_RC(RTProcWait(hProc, RTPROCWAIT_FLAGS_BLOCK, &ProcStatus), VINF_SUCCESS);
    RTThreadSleep(10);

    if (ProcStatus.enmReason != RTPROCEXITREASON_NORMAL || ProcStatus.iStatus != 0)
        RTTestIFailed("enmReason=%d iStatus=%d", ProcStatus.enmReason, ProcStatus.iStatus);
    else if (   offOutput != sizeof("howdy") - 1
             || strcmp(szOutput, "howdy"))
        RTTestIFailed("wrong output: \"%s\" (len=%u)", szOutput, offOutput);
    else
        RTTestIPassed(NULL);
}
RTR3DECL(int) RTProcDaemonize(const char * const *papszArgs, const char *pszDaemonizedOpt)
{
    /*
     * Get the executable name.
     * If this asserts, it's probably because rtR3Init hasn't been called.
     */
    char szExecPath[RTPATH_MAX];
    AssertReturn(RTProcGetExecutablePath(szExecPath, sizeof(szExecPath)) == szExecPath, VERR_WRONG_ORDER);

    /*
     * Create a copy of the argument list with the daemonized option appended.
     */
    unsigned cArgs = 0;
    while (papszArgs[cArgs])
        cArgs++;

    char const **papszNewArgs = (char const **)RTMemAlloc(sizeof(const char *) * (cArgs + 2));
    if (!papszNewArgs)
        return VERR_NO_MEMORY;
    for (unsigned i = 0; i < cArgs; i++)
        papszNewArgs[i] = papszArgs[i];
    papszNewArgs[cArgs] = pszDaemonizedOpt;
    papszNewArgs[cArgs + 1] = NULL;

    /*
     * Open the bitbucket handles and create the detached process.
     */
    RTHANDLE hStdIn;
    int rc = RTFileOpenBitBucket(&hStdIn.u.hFile, RTFILE_O_READ);
    if (RT_SUCCESS(rc))
    {
        hStdIn.enmType = RTHANDLETYPE_FILE;

        RTHANDLE hStdOutAndErr;
        rc = RTFileOpenBitBucket(&hStdOutAndErr.u.hFile, RTFILE_O_WRITE);
        if (RT_SUCCESS(rc))
        {
            hStdOutAndErr.enmType = RTHANDLETYPE_FILE;

            rc = RTProcCreateEx(szExecPath, papszNewArgs, RTENV_DEFAULT,
                                RTPROC_FLAGS_DETACHED | RTPROC_FLAGS_SAME_CONTRACT,
                                &hStdIn, &hStdOutAndErr, &hStdOutAndErr,
                                NULL /*pszAsUser*/,  NULL /*pszPassword*/, NULL /*phProcess*/);

            RTFileClose(hStdOutAndErr.u.hFile);
        }

        RTFileClose(hStdOutAndErr.u.hFile);
    }
    RTMemFree(papszNewArgs);
    return rc;
}
Ejemplo n.º 5
0
static void tstRTCreateProcEx4(const char *pszAsUser, const char *pszPassword)
{
    RTTestISub("Argument with spaces and stuff");

    RTPROCESS hProc;
    RTTESTI_CHECK_RC_RETV(RTProcCreateEx(g_szExecName, g_apszArgs4, RTENV_DEFAULT, 0 /*fFlags*/, NULL,
                                         NULL, NULL, pszAsUser, pszPassword, &hProc), VINF_SUCCESS);
    RTPROCSTATUS ProcStatus = { -1, RTPROCEXITREASON_ABEND };
    RTTESTI_CHECK_RC(RTProcWait(hProc, RTPROCWAIT_FLAGS_BLOCK, &ProcStatus), VINF_SUCCESS);

    if (ProcStatus.enmReason != RTPROCEXITREASON_NORMAL || ProcStatus.iStatus != 0)
        RTTestIFailed("enmReason=%d iStatus=%d", ProcStatus.enmReason, ProcStatus.iStatus);
}
Ejemplo n.º 6
0
/**
 * Runs a process, waiting for it to complete.
 *
 * @returns IPRT status code
 *
 * @param   pszExec     Executable image to use to create the child process.
 * @param   papszArgs   Pointer to an array of arguments to the child.  The
 *                      array terminated by an entry containing NULL.
 * @param   hEnv        Handle to the environment block for the child.
 * @param   fFlags      A combination of RTPROCEXEC_FLAGS_XXX.
 * @param   pStatus     Where to return the status on success.
 */
int RTProcExec(const char *pszExec, const char * const *papszArgs, RTENV hEnv, uint32_t fFlags,
               PRTPROCSTATUS pStatus)
{
    int rc;

    /*
     * Clear output argument (no returning failure here, simply crash!).
     */
    AssertPtr(pStatus);
    pStatus->enmReason = RTPROCEXITREASON_ABEND;
    pStatus->iStatus   = RTEXITCODE_FAILURE;

    /*
     * Check input arguments.
     */
    AssertReturn(!(fFlags & ~RTPROCEXEC_FLAGS_VALID_MASK), VERR_INVALID_PARAMETER);

    /*
     * Set up /dev/null redirections.
     */
    PRTHANDLE   aph[3] = { NULL, NULL, NULL };
    RTHANDLE    ah[3];
    for (uint32_t i = 0; i < 3; i++)
    {
        ah[i].enmType = RTHANDLETYPE_FILE;
        ah[i].u.hFile = NIL_RTFILE;
    }
    rc = VINF_SUCCESS;
    if ((fFlags & RTPROCEXEC_FLAGS_STDIN_NULL) && RT_SUCCESS(rc))
    {
        aph[0] = &ah[0];
        rc = RTFileOpenBitBucket(&ah[0].u.hFile, RTFILE_O_READ);
    }
    if ((fFlags & RTPROCEXEC_FLAGS_STDOUT_NULL) && RT_SUCCESS(rc))
    {
        aph[1] = &ah[1];
        rc = RTFileOpenBitBucket(&ah[1].u.hFile, RTFILE_O_WRITE);
    }
    if ((fFlags & RTPROCEXEC_FLAGS_STDERR_NULL) && RT_SUCCESS(rc))
    {
        aph[2] = &ah[2];
        rc = RTFileOpenBitBucket(&ah[2].u.hFile, RTFILE_O_WRITE);
    }

    /*
     * Create the process.
     */
    RTPROCESS hProc;
    if (RT_SUCCESS(rc))
        rc = RTProcCreateEx(g_szSvnPath,
                            papszArgs,
                            RTENV_DEFAULT,
                            0 /*fFlags*/,
                            aph[0],
                            aph[1],
                            aph[2],
                            NULL /*pszAsUser*/,
                            NULL /*pszPassword*/,
                            &hProc);

    for (uint32_t i = 0; i < 3; i++)
        RTFileClose(ah[i].u.hFile);

    if (RT_SUCCESS(rc))
        rc = RTProcWait(hProc, RTPROCWAIT_FLAGS_BLOCK, pStatus);
    return rc;
}
Ejemplo n.º 7
0
/**
 * Runs a process, collecting the standard output and/or standard error.
 *
 *
 * @returns IPRT status code
 * @retval  VERR_NO_TRANSLATION if the output of the program isn't valid UTF-8
 *          or contains a nul character.
 * @retval  VERR_TOO_MUCH_DATA if the process produced too much data.
 *
 * @param   pszExec     Executable image to use to create the child process.
 * @param   papszArgs   Pointer to an array of arguments to the child.  The
 *                      array terminated by an entry containing NULL.
 * @param   hEnv        Handle to the environment block for the child.
 * @param   fFlags      A combination of RTPROCEXEC_FLAGS_XXX.  The @a
 *                      ppszStdOut and @a ppszStdErr parameters takes precedence
 *                      over redirection flags.
 * @param   pStatus     Where to return the status on success.
 * @param   ppszStdOut  Where to return the text written to standard output. If
 *                      NULL then standard output will not be collected and go
 *                      to the standard output handle of the process.
 *                      Free with RTStrFree, regardless of return status.
 * @param   ppszStdErr  Where to return the text written to standard error. If
 *                      NULL then standard output will not be collected and go
 *                      to the standard error handle of the process.
 *                      Free with RTStrFree, regardless of return status.
 */
int RTProcExecToString(const char *pszExec, const char * const *papszArgs, RTENV hEnv, uint32_t fFlags,
                       PRTPROCSTATUS pStatus, char **ppszStdOut, char **ppszStdErr)
{
    int rc2;

    /*
     * Clear output arguments (no returning failure here, simply crash!).
     */
    AssertPtr(pStatus);
    pStatus->enmReason = RTPROCEXITREASON_ABEND;
    pStatus->iStatus   = RTEXITCODE_FAILURE;
    AssertPtrNull(ppszStdOut);
    if (ppszStdOut)
        *ppszStdOut = NULL;
    AssertPtrNull(ppszStdOut);
    if (ppszStdErr)
        *ppszStdErr = NULL;

    /*
     * Check input arguments.
     */
    AssertReturn(!(fFlags & ~RTPROCEXEC_FLAGS_VALID_MASK), VERR_INVALID_PARAMETER);

    /*
     * Do we need a standard input bitbucket?
     */
    int         rc = VINF_SUCCESS;
    PRTHANDLE   phChildStdIn = NULL;
    RTHANDLE    hChildStdIn;
    hChildStdIn.enmType = RTHANDLETYPE_FILE;
    hChildStdIn.u.hFile = NIL_RTFILE;
    if ((fFlags & RTPROCEXEC_FLAGS_STDIN_NULL) && RT_SUCCESS(rc))
    {
        phChildStdIn = &hChildStdIn;
        rc = RTFileOpenBitBucket(&hChildStdIn.u.hFile, RTFILE_O_READ);
    }

    /*
     * Create the output pipes / bitbuckets.
     */
    RTPIPE      hPipeStdOutR  = NIL_RTPIPE;
    PRTHANDLE   phChildStdOut = NULL;
    RTHANDLE    hChildStdOut;
    hChildStdOut.enmType = RTHANDLETYPE_PIPE;
    hChildStdOut.u.hPipe = NIL_RTPIPE;
    if (ppszStdOut && RT_SUCCESS(rc))
    {
        phChildStdOut = &hChildStdOut;
        rc = RTPipeCreate(&hPipeStdOutR, &hChildStdOut.u.hPipe, 0 /*fFlags*/);
    }
    else if ((fFlags & RTPROCEXEC_FLAGS_STDOUT_NULL) && RT_SUCCESS(rc))
    {
        phChildStdOut = &hChildStdOut;
        hChildStdOut.enmType = RTHANDLETYPE_FILE;
        hChildStdOut.u.hFile = NIL_RTFILE;
        rc = RTFileOpenBitBucket(&hChildStdOut.u.hFile, RTFILE_O_WRITE);
    }

    RTPIPE      hPipeStdErrR  = NIL_RTPIPE;
    PRTHANDLE   phChildStdErr = NULL;
    RTHANDLE    hChildStdErr;
    hChildStdErr.enmType = RTHANDLETYPE_PIPE;
    hChildStdErr.u.hPipe = NIL_RTPIPE;
    if (ppszStdErr && RT_SUCCESS(rc))
    {
        phChildStdErr = &hChildStdErr;
        rc = RTPipeCreate(&hPipeStdErrR, &hChildStdErr.u.hPipe, 0 /*fFlags*/);
    }
    else if ((fFlags & RTPROCEXEC_FLAGS_STDERR_NULL) && RT_SUCCESS(rc))
    {
        phChildStdErr = &hChildStdErr;
        hChildStdErr.enmType = RTHANDLETYPE_FILE;
        hChildStdErr.u.hFile = NIL_RTFILE;
        rc = RTFileOpenBitBucket(&hChildStdErr.u.hFile, RTFILE_O_WRITE);
    }

    if (RT_SUCCESS(rc))
    {
        RTPOLLSET hPollSet;
        rc = RTPollSetCreate(&hPollSet);
        if (RT_SUCCESS(rc))
        {
            if (hPipeStdOutR != NIL_RTPIPE && RT_SUCCESS(rc))
                rc = RTPollSetAddPipe(hPollSet, hPipeStdOutR, RTPOLL_EVT_READ | RTPOLL_EVT_ERROR, 1);
            if (hPipeStdErrR != NIL_RTPIPE)
                rc = RTPollSetAddPipe(hPollSet, hPipeStdErrR, RTPOLL_EVT_READ | RTPOLL_EVT_ERROR, 2);
        }
        if (RT_SUCCESS(rc))
        {
            /*
             * Create the process.
             */
            RTPROCESS hProc;
            rc = RTProcCreateEx(g_szSvnPath,
                                papszArgs,
                                RTENV_DEFAULT,
                                0 /*fFlags*/,
                                NULL /*phStdIn*/,
                                phChildStdOut,
                                phChildStdErr,
                                NULL /*pszAsUser*/,
                                NULL /*pszPassword*/,
                                &hProc);
            rc2 = RTHandleClose(&hChildStdErr); AssertRC(rc2);
            rc2 = RTHandleClose(&hChildStdOut); AssertRC(rc2);

            if (RT_SUCCESS(rc))
            {
                /*
                 * Process output and wait for the process to finish.
                 */
                size_t cbStdOut  = 0;
                size_t offStdOut = 0;
                size_t cbStdErr  = 0;
                size_t offStdErr = 0;
                for (;;)
                {
                    if (hPipeStdOutR != NIL_RTPIPE)
                        rc = rtProcProcessOutput(rc, &hPipeStdOutR, &cbStdOut, &offStdOut, ppszStdOut, hPollSet, 1);
                    if (hPipeStdErrR != NIL_RTPIPE)
                        rc = rtProcProcessOutput(rc, &hPipeStdErrR, &cbStdErr, &offStdErr, ppszStdErr, hPollSet, 2);
                    if (hPipeStdOutR == NIL_RTPIPE && hPipeStdErrR == NIL_RTPIPE)
                        break;

                    if (hProc != NIL_RTPROCESS)
                    {
                        rc2 = RTProcWait(hProc, RTPROCWAIT_FLAGS_NOBLOCK, pStatus);
                        if (rc2 != VERR_PROCESS_RUNNING)
                        {
                            if (RT_FAILURE(rc2))
                                rc = rc2;
                            hProc = NIL_RTPROCESS;
                        }
                    }

                    rc2 = RTPoll(hPollSet, 10000, NULL, NULL);
                    Assert(RT_SUCCESS(rc2) || rc2 == VERR_TIMEOUT);
                }

                if (RT_SUCCESS(rc))
                {
                    if (   (ppszStdOut && *ppszStdOut && !RTStrIsValidEncoding(*ppszStdOut))
                        || (ppszStdErr && *ppszStdErr && !RTStrIsValidEncoding(*ppszStdErr)) )
                        rc = VERR_NO_TRANSLATION;
                }

                /*
                 * No more output, just wait for it to finish.
                 */
                if (hProc != NIL_RTPROCESS)
                {
                    rc2 = RTProcWait(hProc, RTPROCWAIT_FLAGS_BLOCK, pStatus);
                    if (RT_FAILURE(rc2))
                        rc = rc2;
                }
            }
            RTPollSetDestroy(hPollSet);
        }
    }

    rc2 = RTHandleClose(&hChildStdErr); AssertRC(rc2);
    rc2 = RTHandleClose(&hChildStdOut); AssertRC(rc2);
    rc2 = RTHandleClose(&hChildStdIn);  AssertRC(rc2);
    rc2 = RTPipeClose(hPipeStdErrR);    AssertRC(rc2);
    rc2 = RTPipeClose(hPipeStdOutR);    AssertRC(rc2);
    return rc;
}
static void tstRTCreateProcEx6(const char *pszAsUser, const char *pszPassword)
{
    RTTestISub("Profile environment");

    const char *apszArgs[5] =
    {
        g_szExecName,
        "--testcase-child-6",
        "inherit",
        pszAsUser,
        NULL
    };

    RTTESTI_CHECK_RC_RETV(RTEnvSetEx(RTENV_DEFAULT, "testcase-child-6", "true"), VINF_SUCCESS);

    /* Use the process environment first. */
    RTPROCESS hProc;
    RTTESTI_CHECK_RC_RETV(RTProcCreateEx(g_szExecName, apszArgs, RTENV_DEFAULT, 0 /*fFlags*/,
                                         NULL, NULL, NULL, pszAsUser, pszPassword, &hProc), VINF_SUCCESS);
    RTPROCSTATUS ProcStatus = { -1, RTPROCEXITREASON_ABEND };
    RTTESTI_CHECK_RC(RTProcWait(hProc, RTPROCWAIT_FLAGS_BLOCK, &ProcStatus), VINF_SUCCESS);

    if (ProcStatus.enmReason != RTPROCEXITREASON_NORMAL || ProcStatus.iStatus != 0)
        RTTestIFailed("enmReason=%d iStatus=%d", ProcStatus.enmReason, ProcStatus.iStatus);

    /* Use the process environment first with a little change. */
    apszArgs[2] = "change-record";
    RTENV hEnvChange;
    RTTESTI_CHECK_RC_RETV(RTEnvCreateChangeRecord(&hEnvChange), VINF_SUCCESS);
    RTTESTI_CHECK_RC_RETV(RTEnvSetEx(hEnvChange, "testcase-child-6", "changed"), VINF_SUCCESS);
    int rc;
    RTTESTI_CHECK_RC(rc = RTProcCreateEx(g_szExecName, apszArgs, hEnvChange, RTPROC_FLAGS_ENV_CHANGE_RECORD,
                                         NULL, NULL, NULL, pszAsUser, pszPassword, &hProc), VINF_SUCCESS);
    if (RT_SUCCESS(rc))
    {
        ProcStatus.enmReason = RTPROCEXITREASON_ABEND;
        ProcStatus.iStatus   = -1;
        RTTESTI_CHECK_RC(RTProcWait(hProc, RTPROCWAIT_FLAGS_BLOCK, &ProcStatus), VINF_SUCCESS);

        if (ProcStatus.enmReason != RTPROCEXITREASON_NORMAL || ProcStatus.iStatus != 0)
            RTTestIFailed("enmReason=%d iStatus=%d", ProcStatus.enmReason, ProcStatus.iStatus);
    }


    /* Use profile environment this time. */
    apszArgs[2] = "noinherit";
    RTTESTI_CHECK_RC(rc = RTProcCreateEx(g_szExecName, apszArgs, RTENV_DEFAULT, RTPROC_FLAGS_PROFILE,
                                         NULL, NULL, NULL, pszAsUser, pszPassword, &hProc), VINF_SUCCESS);
    if (RT_SUCCESS(rc))
    {
        ProcStatus.enmReason = RTPROCEXITREASON_ABEND;
        ProcStatus.iStatus   = -1;
        RTTESTI_CHECK_RC(RTProcWait(hProc, RTPROCWAIT_FLAGS_BLOCK, &ProcStatus), VINF_SUCCESS);

        if (ProcStatus.enmReason != RTPROCEXITREASON_NORMAL || ProcStatus.iStatus != 0)
            RTTestIFailed("enmReason=%d iStatus=%d", ProcStatus.enmReason, ProcStatus.iStatus);
    }

    /* Use profile environment this time. */
    apszArgs[2] = "noinherit-change-record";
    RTTESTI_CHECK_RC(rc = RTProcCreateEx(g_szExecName, apszArgs, hEnvChange,
                                         RTPROC_FLAGS_PROFILE | RTPROC_FLAGS_ENV_CHANGE_RECORD,
                                         NULL, NULL, NULL, pszAsUser, pszPassword, &hProc), VINF_SUCCESS);
    if (RT_SUCCESS(rc))
    {
        ProcStatus.enmReason = RTPROCEXITREASON_ABEND;
        ProcStatus.iStatus   = -1;
        RTTESTI_CHECK_RC(RTProcWait(hProc, RTPROCWAIT_FLAGS_BLOCK, &ProcStatus), VINF_SUCCESS);

        if (ProcStatus.enmReason != RTPROCEXITREASON_NORMAL || ProcStatus.iStatus != 0)
            RTTestIFailed("enmReason=%d iStatus=%d", ProcStatus.enmReason, ProcStatus.iStatus);
    }


    RTTESTI_CHECK_RC(RTEnvDestroy(hEnvChange), VINF_SUCCESS);

    /*
     * Restore the environment and check that the PROFILE flag didn't mess with
     * the process environment.  (Note! The bug may be elsewhere as well.)
     */
    RTTESTI_CHECK_RC(RTEnvUnsetEx(RTENV_DEFAULT, "testcase-child-6"), VINF_SUCCESS);

    RTENV hEnvCur;
    RTTESTI_CHECK_RC_RETV(RTEnvClone(&hEnvCur, RTENV_DEFAULT), VINF_SUCCESS);
    uint32_t cCurrent = RTEnvCountEx(hEnvCur);
    uint32_t cInitial = RTEnvCountEx(g_hEnvInitial);
    RTTESTI_CHECK_MSG(cCurrent == cInitial, ("cCurrent=%u cInitial=%u\n", cCurrent, cInitial));
    uint32_t    cVars1;
    RTENV       hEnv1,    hEnv2;
    const char *pszEnv1, *pszEnv2;
    if (cCurrent >= cInitial)
    {
        hEnv1   = hEnvCur;
        pszEnv1 = "current";
        cVars1  = cCurrent;
        hEnv2   = g_hEnvInitial;
        pszEnv2 = "initial";
    }
    else
    {
        hEnv2   = hEnvCur;
        pszEnv2 = "current";
        hEnv1   = g_hEnvInitial;
        pszEnv1 = "initial";
        cVars1  = cInitial;
    }
    for (uint32_t i = 0; i < cVars1; i++)
    {
        char szValue1[_16K];
        char szVarNm[_1K];
        rc = RTEnvGetByIndexEx(hEnv1, i, szVarNm, sizeof(szVarNm), szValue1, sizeof(szValue1));
        if (RT_SUCCESS(rc))
        {
            char szValue2[_16K];
            rc = RTEnvGetEx(hEnv2, szVarNm, szValue2, sizeof(szValue2), NULL);
            if (RT_SUCCESS(rc))
            {
                if (strcmp(szValue1, szValue2) != 0)
                {
                    RTTestIFailed("Variable '%s' differs", szVarNm);
                    RTTestIFailureDetails("%s: '%s'\n"
                                          "%s: '%s'\n",
                                          pszEnv1, szValue1,
                                          pszEnv2, szValue2);
                }
            }
            else
                RTTestIFailed("RTEnvGetEx(%s,%s,,) failed: %Rrc", pszEnv2, szVarNm, rc);

        }
        else
            RTTestIFailed("RTEnvGetByIndexEx(%s,%u,,,,) failed: %Rrc", pszEnv1, i, rc);
    }
}