From: VMware, Inc <> Date: Thu, 15 Oct 2009 21:05:19 +0000 (-0700) Subject: Run programs in a user environment without Tools' settings. X-Git-Tag: 2009.10.15-201664~23 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=55af33a6d4fecd8b176dcbdd08b00bf0644f4c58;p=thirdparty%2Fopen-vm-tools.git Run programs in a user environment without Tools' settings. vmware-user on Linux uses the app-loader tool to set up its environment so it can use various bundled libraries. However, this cause these environment variables to be set that when VIX runs programs or scripts from vmware-user, these processes fail due to library conflicts. For example, gedit on RHEL 4 was compiled with a version of libpango that conflicts with the version of this library that ships with the Tools. The solution is to store the original, pre-app-loader environment with the VIX tools module. Some of the infrastructure was already in place to fix a similar issue when launching a web browser to view the Help from toolbox. However, this needed to be plumbed through VIX, and added to lib/procMgr. Also, one further wrinkle is that VIX allows clients to programatically change the environment. So, we keep track of the environment using a hash table, update it when processing WriteVariable commands, then serial the hashtable into a environ block when RunProgram or RunScript are called. Signed-off-by: Marcelo Vanzin --- diff --git a/open-vm-tools/lib/include/procMgr.h b/open-vm-tools/lib/include/procMgr.h index 150214e53..54833e603 100644 --- a/open-vm-tools/lib/include/procMgr.h +++ b/open-vm-tools/lib/include/procMgr.h @@ -59,36 +59,36 @@ typedef struct ProcMgr_ProcList { } ProcMgr_ProcList; -#if defined(_WIN32) -/* - * If a caller needs to use a non-default set of arguments for - * CreateProcess[AsUser] in ProcMgr_Exec[A]sync, this structure should be used. - * - * - If 'userArgs' is NULL, defaults are used: - * - bInheritHandles defaults to TRUE - * - lpStartupInfo is instantiated and initialized with: - * - cb initialized to size of the object - * - dwFlags initialized to STARTF_USESHOWWINDOW - * - wShowWindow initialized to SW_MINIMIZE. - * - defaults for all other parameters are NULL/FALSE - * - * - If 'userArgs' is not NULL, the values in the 'userArgs' object are used - * according to the following rules: - * - If lpStartupInfo is NULL, it is instantiated and initialized with: - * - cb initialized to size of the object - * - dwFlags initialized to STARTF_USESHOWWINDOW - * - wShowWindow initialized to SW_MINIMIZE. - * - The caller would need to do some of this initialization if they set - * lpStartupInfo. - * - If hToken is set: - * - if lpStartupInfo->lpDesktop is not NULL, then it is used directly. Otherwise, - * lpStartupInfo->lpDesktop is initialized appropriately. - * - * XXX: Make it more convenient for callers(like ToolsDaemonTcloRunProgramImpl) - * to set just wShowWindow without needing to instantiate and initialize a - * STARTUPINFO object. - */ typedef struct ProcMgr_ProcArgs { +#if defined(_WIN32) + /* + * If a caller needs to use a non-default set of arguments for + * CreateProcess[AsUser] in ProcMgr_Exec[A]sync, this structure should be used. + * + * - If 'userArgs' is NULL, defaults are used: + * - bInheritHandles defaults to TRUE + * - lpStartupInfo is instantiated and initialized with: + * - cb initialized to size of the object + * - dwFlags initialized to STARTF_USESHOWWINDOW + * - wShowWindow initialized to SW_MINIMIZE. + * - defaults for all other parameters are NULL/FALSE + * + * - If 'userArgs' is not NULL, the values in the 'userArgs' object are used + * according to the following rules: + * - If lpStartupInfo is NULL, it is instantiated and initialized with: + * - cb initialized to size of the object + * - dwFlags initialized to STARTF_USESHOWWINDOW + * - wShowWindow initialized to SW_MINIMIZE. + * - The caller would need to do some of this initialization if they set + * lpStartupInfo. + * - If hToken is set: + * - if lpStartupInfo->lpDesktop is not NULL, then it is used directly. Otherwise, + * lpStartupInfo->lpDesktop is initialized appropriately. + * + * XXX: Make it more convenient for callers(like ToolsDaemonTcloRunProgramImpl) + * to set just wShowWindow without needing to instantiate and initialize a + * STARTUPINFO object. + */ HANDLE hToken; LPCWSTR lpApplicationName; @@ -99,11 +99,16 @@ typedef struct ProcMgr_ProcArgs { LPVOID lpEnvironment; LPCWSTR lpCurrentDirectory; LPSTARTUPINFO lpStartupInfo; -} ProcMgr_ProcArgs; #else -/* Placeholder type for non win32 platforms. Not used. */ -typedef void * ProcMgr_ProcArgs; + /* + * The environment variables to run the program with. If NULL, use the current + * environment. + */ + char **envp; #endif +} ProcMgr_ProcArgs; + + typedef void ProcMgr_Callback(Bool status, void *clientData); diff --git a/open-vm-tools/lib/include/vixTools.h b/open-vm-tools/lib/include/vixTools.h index 7215b915f..8a4a73a0e 100644 --- a/open-vm-tools/lib/include/vixTools.h +++ b/open-vm-tools/lib/include/vixTools.h @@ -38,6 +38,7 @@ typedef void (*VixToolsReportProgramDoneProcType)(const char *requestName, void *clientData); VixError VixTools_Initialize(Bool thisProcessRunsAsRootArg, + const char * const *originalEnvp, VixToolsReportProgramDoneProcType reportProgramDoneProc, void *clientData); diff --git a/open-vm-tools/lib/procMgr/procMgrPosix.c b/open-vm-tools/lib/procMgr/procMgrPosix.c index 94e7d5a5c..3188b5062 100644 --- a/open-vm-tools/lib/procMgr/procMgrPosix.c +++ b/open-vm-tools/lib/procMgr/procMgrPosix.c @@ -69,6 +69,7 @@ #include "str.h" #include "fileIO.h" #include "codeset.h" +#include "unicode.h" /* @@ -97,7 +98,8 @@ struct ProcMgr_AsyncProc { int exitCode; }; -static pid_t ProcMgrStartProcess(char const *cmd); +static pid_t ProcMgrStartProcess(char const *cmd, + char * const *envp); static Bool ProcMgrWaitForProcCompletion(pid_t pid, Bool *validExitCode, @@ -573,13 +575,13 @@ ProcMgr_FreeProcList(ProcMgr_ProcList *procList) Bool ProcMgr_ExecSync(char const *cmd, // IN: UTF-8 command line - ProcMgr_ProcArgs *userArgs) // IN: Unused + ProcMgr_ProcArgs *userArgs) // IN: optional { pid_t pid; Debug("Executing sync command: %s\n", cmd); - pid = ProcMgrStartProcess(cmd); + pid = ProcMgrStartProcess(cmd, userArgs ? userArgs->envp : NULL); if (pid == -1) { return FALSE; @@ -606,37 +608,54 @@ ProcMgr_ExecSync(char const *cmd, // IN: UTF-8 command line *---------------------------------------------------------------------- */ -static pid_t -ProcMgrStartProcess(char const *cmd) // IN: UTF-8 encoded cmd +static pid_t +ProcMgrStartProcess(char const *cmd, // IN: UTF-8 encoded cmd + char * const *envp) // IN: UTF-8 encoded env vars { pid_t pid; char *cmdCurrent = NULL; + char **envpCurrent = NULL; if (cmd == NULL) { ASSERT(FALSE); return -1; } + /* + * Convert the strings before the call to fork(), since the conversion + * routines may rely on locks that do not survive fork(). + */ + if (!CodeSet_Utf8ToCurrent(cmd, strlen(cmd), &cmdCurrent, NULL)) { Warning("Could not convert from UTF-8 to current\n"); return -1; } + if (NULL != envp) { + envpCurrent = Unicode_GetAllocList(envp, -1, STRING_ENCODING_DEFAULT); + } + pid = fork(); if (pid == -1) { Warning("Unable to fork: %s.\n\n", strerror(errno)); } else if (pid == 0) { + static const char shellPath[] = "/bin/sh"; + char *args[] = { "sh", "-c", cmdCurrent, NULL }; /* * Child */ - execl("/bin/sh", "sh", "-c", cmdCurrent, (char *)NULL); + if (NULL != envpCurrent) { + execve(shellPath, args, envpCurrent); + } else { + execv(shellPath, args); + } /* Failure */ Panic("Unable to execute the \"%s\" shell command: %s.\n\n", - cmdCurrent, strerror(errno)); + cmd, strerror(errno)); } /* @@ -644,6 +663,7 @@ ProcMgrStartProcess(char const *cmd) // IN: UTF-8 encoded cmd */ free(cmdCurrent); + Unicode_FreeList(envpCurrent, -1); return pid; } @@ -732,7 +752,7 @@ ProcMgrWaitForProcCompletion(pid_t pid, // IN ProcMgr_AsyncProc * ProcMgr_ExecAsync(char const *cmd, // IN: UTF-8 command line - ProcMgr_ProcArgs *userArgs) // IN: Unused + ProcMgr_ProcArgs *userArgs) // IN: optional { ProcMgr_AsyncProc *asyncProc = NULL; pid_t pid; @@ -799,7 +819,7 @@ ProcMgr_ExecAsync(char const *cmd, // IN: UTF-8 command line * Only run the program if we have not already experienced a failure. */ if (status) { - childPid = ProcMgrStartProcess(cmd); + childPid = ProcMgrStartProcess(cmd, userArgs ? userArgs->envp : NULL); status = childPid != -1; } diff --git a/open-vm-tools/lib/vixTools/vixTools.c b/open-vm-tools/lib/vixTools/vixTools.c index a961653d1..b7523bf30 100644 --- a/open-vm-tools/lib/vixTools/vixTools.c +++ b/open-vm-tools/lib/vixTools/vixTools.c @@ -77,6 +77,7 @@ #include "codeset.h" #include "posix.h" #include "unicode.h" +#include "hashTable.h" #if defined(linux) || defined(_WIN32) #include "netutil.h" @@ -144,6 +145,18 @@ static Bool allowConsoleUserOps = FALSE; static VixToolsReportProgramDoneProcType reportProgramDoneProc = NULL; static void *reportProgramDoneData = NULL; +#ifndef _WIN32 +typedef struct VixToolsEnvironmentTableIterator { + char **envp; + size_t pos; +} VixToolsEnvironmentTableIterator; + +/* + * Stores the environment variables to use when executing guest applications. + */ +static HashTable *userEnvironmentTable = NULL; +#endif + static VixError VixToolsGetFileInfo(VixCommandRequestHeader *requestMsg, char **result); @@ -247,6 +260,17 @@ static VixError VixToolsDoesUsernameMatchCurrentUser(const char *username); static Bool VixToolsPidRefersToThisProcess(ProcMgr_Pid pid); +#ifndef _WIN32 +static void VixToolsBuildUserEnvironmentTable(const char * const *envp); + +static char **VixToolsEnvironmentTableToEnvp(const HashTable *envTable); + +static int VixToolsEnvironmentTableEntryToEnvpEntry(const char *key, void *value, + void *clientData); + +static void VixToolsFreeEnvp(char **envp); +#endif + /* *----------------------------------------------------------------------------- @@ -265,6 +289,7 @@ static Bool VixToolsPidRefersToThisProcess(ProcMgr_Pid pid); VixError VixTools_Initialize(Bool thisProcessRunsAsRootParam, // IN + const char * const *originalEnvp, // IN VixToolsReportProgramDoneProcType reportProgramDoneProcParam, // IN void *clientData) // IN { @@ -274,10 +299,197 @@ VixTools_Initialize(Bool thisProcessRunsAsRootParam, reportProgramDoneProc = reportProgramDoneProcParam; reportProgramDoneData = clientData; +#ifndef _WIN32 + VixToolsBuildUserEnvironmentTable(originalEnvp); +#endif + return(err); } // VixTools_Initialize +#ifndef _WIN32 +/* + *----------------------------------------------------------------------------- + * + * VixToolsBuildUserEnvironmentTable -- + * + * Takes an array of strings of the form "=" storing the + * environment variables (as per environ(7)) that should be used when + * running programs, and populates the hash table with them. + * + * If 'envp' is NULL, skip creating the user environment table, so that + * we just use the current environment. + * + * Results: + * None + * + * Side effects: + * May initialize the global userEnvironmentTable. + * + *----------------------------------------------------------------------------- + */ + +static void +VixToolsBuildUserEnvironmentTable(const char * const *envp) // IN: optional +{ + if (NULL == envp) { + ASSERT(NULL == userEnvironmentTable); + return; + } + + if (NULL == userEnvironmentTable) { + userEnvironmentTable = HashTable_Alloc(64, // buckets (power of 2) + HASH_STRING_KEY | HASH_FLAG_COPYKEY, + free); // freeFn for the values + } else { + /* + * If we're being reinitialized, we can just clear the table and + * load the new values into it. They shouldn't have changed, but + * in case they ever do this will cover it. + */ + HashTable_Clear(userEnvironmentTable); + } + + for (; NULL != *envp; envp++) { + char *name; + char *value; + char *whereToSplit; + size_t nameLen; + + whereToSplit = strchr(*envp, '='); + if (NULL == whereToSplit) { + /* Our code generated this list, so this shouldn't happen. */ + ASSERT(0); + continue; + } + + nameLen = whereToSplit - *envp; + name = Util_SafeMalloc(nameLen + 1); + memcpy(name, *envp, nameLen); + name[nameLen] = '\0'; + + whereToSplit++; // skip over '=' + + value = Util_SafeStrdup(whereToSplit); + + HashTable_Insert(userEnvironmentTable, name, value); + DEBUG_ONLY(value = NULL;) // the hash table now owns 'value' + + free(name); + DEBUG_ONLY(name = NULL;) + } +} + + +/* + *----------------------------------------------------------------------------- + * + * VixToolsEnvironmentTableToEnvp -- + * + * Take a hash table storing environment variables names and values and + * build an array out of them. + * + * Results: + * char ** - envp array as per environ(7). Must be freed using + * VixToolsFreeEnvp + * + * Side effects: + * None + * + *----------------------------------------------------------------------------- + */ + +static char ** +VixToolsEnvironmentTableToEnvp(const HashTable *envTable) // IN +{ + char **envp; + + if (NULL != envTable) { + VixToolsEnvironmentTableIterator itr; + size_t numEntries = HashTable_GetNumElements(envTable); + + itr.envp = envp = Util_SafeMalloc((numEntries + 1) * sizeof *envp); + itr.pos = 0; + + HashTable_ForEach(envTable, VixToolsEnvironmentTableEntryToEnvpEntry, &itr); + + ASSERT(numEntries == itr.pos); + + envp[numEntries] = NULL; + } else { + envp = NULL; + } + + return envp; +} + + +/* + *----------------------------------------------------------------------------- + * + * VixToolsEnvironmentTableEntryToEnvpEntry -- + * + * Callback for HashTable_ForEach(). Gets called for each entry in an + * environment table, converting the key (environment variable name) and + * value (environment variable value) into a string of the form + * "=" and adding that to the envp array passed in with the + * VixToolsEnvironmentTableIterator client data. + * + * Results: + * int - always 0 + * + * Side effects: + * Sets one entry in the envp. + * + *----------------------------------------------------------------------------- + */ + +static int +VixToolsEnvironmentTableEntryToEnvpEntry(const char *key, // IN + void *value, // IN + void *clientData) // IN/OUT +{ + VixToolsEnvironmentTableIterator *itr = clientData; + + itr->envp[itr->pos++] = Str_SafeAsprintf(NULL, "%s=%s", key, (char *)value); + + return 0; +} + + +/* + *----------------------------------------------------------------------------- + * + * VixToolsFreeEnvp -- + * + * Free's an array of strings where both the strings and the array + * were heap allocated. + * + * Results: + * None + * + * Side effects: + * None + * + *----------------------------------------------------------------------------- + */ + +static void +VixToolsFreeEnvp(char **envp) // IN +{ + if (NULL != envp) { + char **itr; + + for (itr = envp; NULL != *itr; itr++) { + free(*itr); + } + + free(envp); + } +} +#endif // #ifndef _WIN32 + + /* *----------------------------------------------------------------------------- * @@ -449,9 +661,9 @@ VixToolsRunProgramImpl(char *requestName, // IN char *stopProgramFileName; Bool programExists; Bool programIsExecutable; + ProcMgr_ProcArgs procArgs; #if defined(_WIN32) Bool forcedRoot = FALSE; - ProcMgr_ProcArgs procArgs; STARTUPINFO si; #endif #if defined(VMTOOLS_USE_GLIB) @@ -551,15 +763,19 @@ VixToolsRunProgramImpl(char *requestName, // IN si.dwFlags = STARTF_USESHOWWINDOW; si.wShowWindow = (VIX_RUNPROGRAM_ACTIVATE_WINDOW & runProgramOptions) ? SW_SHOWNORMAL : SW_MINIMIZE; - asyncState->procState = ProcMgr_ExecAsync(fullCommandLine, &procArgs); #else - asyncState->procState = ProcMgr_ExecAsync(fullCommandLine, NULL); + procArgs.envp = VixToolsEnvironmentTableToEnvp(userEnvironmentTable); #endif + asyncState->procState = ProcMgr_ExecAsync(fullCommandLine, &procArgs); + #if defined(_WIN32) if (forcedRoot) { Impersonate_UnforceRoot(); } +#else + VixToolsFreeEnvp(procArgs.envp); + DEBUG_ONLY(procArgs.envp = NULL;) #endif if (NULL == asyncState->procState) { @@ -1718,6 +1934,23 @@ VixToolsReadVariable(VixCommandRequestHeader *requestMsg, // IN * Alwasy get environment variable for the current user, even if the * current user is root/administrator */ +#ifndef _WIN32 + /* + * If we are maintaining our own set of environment variables + * because the application we're running from changed the user's + * environment, then we should be reading from that. + */ + if (NULL != userEnvironmentTable) { + if (HashTable_Lookup(userEnvironmentTable, valueName, + (void **) &value)) { + value = Util_SafeStrdup(value); + } else { + value = Util_SafeStrdup(""); + } + break; + } +#endif + value = System_GetEnv(FALSE, valueName); if (NULL == value) { value = Util_SafeStrdup(""); @@ -1806,7 +2039,25 @@ VixToolsWriteVariable(VixCommandRequestHeader *requestMsg) // IN result = System_SetEnv(FALSE, valueName, value); if (0 != result) { err = FoundryToolsDaemon_TranslateSystemErr(); + goto abort; } + +#ifndef _WIN32 + /* + * We need to make sure that this change is reflected in the table of + * environment variables we use when launching programs. This is so if a + * a user sets LD_LIBRARY_PATH with WriteVariable, and then calls + * RunProgramInGuest, that program will see the new value. + */ + if (NULL != userEnvironmentTable) { + /* + * The hash table will make a copy of valueName, but we have to supply + * a deep copy of the value. + */ + HashTable_ReplaceOrInsert(userEnvironmentTable, valueName, + Util_SafeStrdup(value)); + } +#endif break; case VIX_GUEST_CONFIG: @@ -2486,9 +2737,9 @@ VixToolsRunScript(VixCommandRequestHeader *requestMsg, // IN static char resultBuffer[32]; VixMsgRunScriptRequest *scriptRequest; const char *interpreterFlags = ""; + ProcMgr_ProcArgs procArgs; #if defined(_WIN32) Bool forcedRoot = FALSE; - ProcMgr_ProcArgs procArgs; #endif #if defined(VMTOOLS_USE_GLIB) GSource *timer; @@ -2673,15 +2924,19 @@ if (0 == *interpreterName) { memset(&procArgs, 0, sizeof procArgs); procArgs.hToken = (PROCESS_CREATOR_USER_TOKEN == userToken) ? NULL : userToken; procArgs.bInheritHandles = TRUE; - asyncState->procState = ProcMgr_ExecAsync(fullCommandLine, &procArgs); #else - asyncState->procState = ProcMgr_ExecAsync(fullCommandLine, NULL); + procArgs.envp = VixToolsEnvironmentTableToEnvp(userEnvironmentTable); #endif + asyncState->procState = ProcMgr_ExecAsync(fullCommandLine, &procArgs); + #if defined(_WIN32) if (forcedRoot) { Impersonate_UnforceRoot(); } +#else + VixToolsFreeEnvp(procArgs.envp); + DEBUG_ONLY(procArgs.envp = NULL;) #endif if (NULL == asyncState->procState) { @@ -4376,5 +4631,3 @@ VixToolsEnableStaticOnPrimary(const char *ipAddr, // IN return ret; } #endif - - diff --git a/open-vm-tools/services/plugins/vix/foundryToolsDaemon.c b/open-vm-tools/services/plugins/vix/foundryToolsDaemon.c index dab5a5714..e72af25b0 100644 --- a/open-vm-tools/services/plugins/vix/foundryToolsDaemon.c +++ b/open-vm-tools/services/plugins/vix/foundryToolsDaemon.c @@ -446,7 +446,14 @@ void FoundryToolsDaemon_Initialize(ToolsAppCtx *ctx) { thisProcessRunsAsRoot = (strcmp(ctx->name, VMTOOLS_GUEST_SERVICE) == 0); + + /* + * TODO: Add the original/native environment (envp) to ToolsAppContext so + * we can know what the environment variables were before the loader scripts + * changed them. + */ (void) VixTools_Initialize(thisProcessRunsAsRoot, + NULL, // envp ToolsDaemonTcloReportProgramCompleted, ctx); @@ -477,10 +484,11 @@ FoundryToolsDaemon_Initialize(ToolsAppCtx *ctx) */ void -FoundryToolsDaemon_RegisterRoutines(RpcIn *in, // IN - GuestApp_Dict **confDictRef, // IN - DblLnkLst_Links *eventQueue, // IN - Bool runAsRoot) // IN +FoundryToolsDaemon_RegisterRoutines(RpcIn *in, // IN + GuestApp_Dict **confDictRef, // IN + DblLnkLst_Links *eventQueue, // IN + const char * const *orginalEnvp, // IN + Bool runAsRoot) // IN { static Bool inited = FALSE; #if defined(linux) || defined(_WIN32) @@ -495,6 +503,7 @@ FoundryToolsDaemon_RegisterRoutines(RpcIn *in, // IN globalEventQueue = eventQueue; (void) VixTools_Initialize(thisProcessRunsAsRoot, + orginalEnvp, ToolsDaemonTcloReportProgramCompleted, NULL); diff --git a/open-vm-tools/services/plugins/vix/foundryToolsDaemon.h b/open-vm-tools/services/plugins/vix/foundryToolsDaemon.h index 5b0fc2acc..962395826 100644 --- a/open-vm-tools/services/plugins/vix/foundryToolsDaemon.h +++ b/open-vm-tools/services/plugins/vix/foundryToolsDaemon.h @@ -45,6 +45,7 @@ FoundryToolsDaemon_Initialize(ToolsAppCtx *ctx); void FoundryToolsDaemon_RegisterRoutines(RpcIn *in, GuestApp_Dict **confDictRef, DblLnkLst_Links *eventQueue, + const char * const *originalEnvp, Bool runAsRoot); /* There isn't an _UnregisterRoutines yet. */ diff --git a/open-vm-tools/vmware-user/vmware-user.cpp b/open-vm-tools/vmware-user/vmware-user.cpp index d4515be15..cc774d563 100644 --- a/open-vm-tools/vmware-user/vmware-user.cpp +++ b/open-vm-tools/vmware-user/vmware-user.cpp @@ -58,6 +58,7 @@ extern "C" { #include "unity.h" #include "ghIntegration.h" #include "resolution.h" +#include "system.h" #include "vm_atomic.h" #include "hostinfo.h" @@ -740,8 +741,11 @@ VMwareUserConfFileLoop(void *clientData) // IN * *----------------------------------------------------------------------------- */ + int -main(int argc, char *argv[]) +main(int argc, // IN + char *argv[], // IN + char *envp[]) // IN { struct sigaction olds[ARRAYSIZE(gSignals)]; int index; @@ -953,10 +957,29 @@ main(int argc, char *argv[]) #if !defined(N_PLAT_NLM) && !defined(sun) { + const char **nativeEnvp; + char **utf8NativeEnvp; + + /* + * Determine our pre-VMware wrapper native environment and pass that to + * Foundry so it can spawn applications. + */ + + nativeEnvp = System_GetNativeEnviron(const_cast(envp)); + + /* Foundry takes its strings UTF-8. */ + utf8NativeEnvp = Unicode_AllocList(const_cast(nativeEnvp), + -1, // nativeEnvp is NULL terminated + STRING_ENCODING_DEFAULT); + FoundryToolsDaemon_RegisterRoutines(gRpcIn, &confDict, gEventQueue, + utf8NativeEnvp, FALSE); + + Unicode_FreeList(utf8NativeEnvp, -1); + System_FreeNativeEnviron(nativeEnvp); } #endif