process-win.cpp revision fc6c63c858c3463dbb52c65abe055673da45a18e
/* $Id$ */
/** @file
* IPRT - Process, Windows.
*/
/*
* Copyright (C) 2006-2012 Oracle Corporation
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* 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_PROCESS
#include <Userenv.h>
#include <Windows.h>
#include <tlhelp32.h>
#include <process.h>
#include <errno.h>
#include <Strsafe.h>
#include <iprt/critsect.h>
#include <iprt/initterm.h>
/*******************************************************************************
* Structures and Typedefs *
*******************************************************************************/
typedef FNPROCESS32FIRST *PFNPROCESS32FIRST;
typedef FNPROCESS32NEXT *PFNPROCESS32NEXT;
typedef FNENUMPROCESSES *PFNENUMPROCESSES;
typedef FNGETMODULEBASENAME *PFNGETMODULEBASENAME;
typedef FNLOADUSERPROFILEW *PFNLOADUSERPROFILEW;
typedef FNUNLOADUSERPROFILE *PFNUNLOADUSERPROFILE;
/*******************************************************************************
* Global Variables *
*******************************************************************************/
/** Init once structure. */
/** Critical section protecting the process array. */
static RTCRITSECT g_CritSect;
/** The number of processes in the array. */
static uint32_t g_cProcesses;
/** The current allocation size. */
static uint32_t g_cProcessesAlloc;
/** Array containing the live or non-reaped child processes. */
static struct RTPROCWINENTRY
{
/** The process ID. */
/** The process handle. */
} *g_paProcesses;
/**
* Clean up the globals.
*
* @param enmReason Ignored.
* @param iStatus Ignored.
* @param pvUser Ignored.
*/
{
size_t i = g_cProcesses;
while (i-- > 0)
{
}
g_cProcesses = 0;
g_cProcessesAlloc = 0;
}
/**
* Initialize the globals.
*
* @returns IPRT status code.
* @param pvUser1 Ignored.
* @param pvUser2 Ignored.
*/
{
g_cProcesses = 0;
g_cProcessesAlloc = 0;
if (RT_SUCCESS(rc))
{
/** @todo init once, terminate once - this is a generic thing which should
* have some kind of static and simpler setup! */
if (RT_SUCCESS(rc))
return rc;
}
return rc;
}
/**
* Gets the process handle for a process from g_paProcesses.
*
* @returns Process handle if found, NULL if not.
* @param pid The process to remove (pid).
*/
{
uint32_t i = g_cProcesses;
while (i-- > 0)
{
break;
}
return hProcess;
}
/**
* Removes a process from g_paProcesses and closes the process handle.
*
* @param pid The process to remove (pid).
*/
{
uint32_t i = g_cProcesses;
while (i-- > 0)
{
g_cProcesses--;
if (cToMove)
return;
}
}
/**
* Adds a process to g_paProcesses.
*
* @returns IPRT status code.
* @param pid The process id.
* @param hProcess The process handle.
*/
{
uint32_t i = g_cProcesses;
if (i >= g_cProcessesAlloc)
{
if (RT_UNLIKELY(!pvNew))
{
return VERR_NO_MEMORY;
}
g_cProcessesAlloc = i + 16;
}
g_cProcesses = i + 1;
return VINF_SUCCESS;
}
RTR3DECL(int) RTProcCreate(const char *pszExec, const char * const *papszArgs, RTENV Env, unsigned fFlags, PRTPROCESS pProcess)
{
pProcess);
}
/**
* Map some important or much used Windows error codes
* to our error codes.
*
* @return Mapped IPRT status code.
* @param dwError Windows error code to map to IPRT code.
*/
{
int rc;
switch (dwError)
{
case ERROR_NOACCESS:
case ERROR_PRIVILEGE_NOT_HELD:
break;
case ERROR_PASSWORD_EXPIRED:
case ERROR_ACCOUNT_RESTRICTION: /* See: http://support.microsoft.com/kb/303846/ */
break;
case ERROR_FILE_CORRUPT:
break;
default:
/* Could trigger a debug assertion! */
break;
}
return rc;
}
/**
* Get the process token of the process indicated by @a dwPID if the @a pSid
* matches.
*
* @returns IPRT status code.
*
* @param dwPid The process identifier.
* @param pSid The secure identifier of the user.
* @param phToken Where to return the a duplicate of the process token
* handle on success. (The caller closes it.)
*/
{
int rc;
{
if (OpenProcessToken(hProc,
&hTokenProc))
{
if ( !fRc
&& dwSize > 0)
{
if (pTokenUser)
{
&dwSize))
{
{
{
/*
* So we found the process instance which belongs to the user we want to
* to run our new process under. This duplicated token will be used for
* the actual CreateProcessAsUserW() call then.
*/
rc = VINF_SUCCESS;
}
else
}
else
rc = VERR_NOT_FOUND;
}
else
}
else
rc = VERR_NO_MEMORY;
}
else
}
else
}
else
return rc;
}
/**
* Fallback method for rtProcWinFindTokenByProcess that uses the older NT4
* PSAPI.DLL API.
*
* @returns Success indicator.
* @param papszNames The process candidates, in prioritized order.
* @param pSid The secure identifier of the user.
* @param phToken Where to return the token handle - duplicate,
* caller closes it on success.
*
* @remarks NT4 needs a copy of "PSAPI.dll" (redistributed by Microsoft and not
* part of the OS) in order to get a lookup. If we don't have this DLL
* we are not able to get a token and therefore no UI will be visible.
*/
static bool rtProcWinFindTokenByProcessAndPsApi(const char * const *papszNames, PSID pSid, PHANDLE phToken)
{
bool fFound = false;
/*
* Load PSAPI.DLL and resolve the two symbols we need.
*/
if (RT_FAILURE_NP(rc))
return false;
if (RT_SUCCESS(rc))
if (RT_SUCCESS(rc))
{
/*
* Get a list of PID. We retry if it looks like there are more PIDs
* to be returned than what we supplied buffer space for.
*/
DWORD cbPidsReturned = 0;
for (;;)
{
{
}
if ( cbPidsReturned < cbPidsAllocated
|| cbPidsAllocated >= _512K)
break;
cbPidsAllocated *= 2;
}
if (RT_SUCCESS(rc))
{
/*
* Search for the process.
*
* We ASSUME that the caller won't be specifying any names longer
* than RTPATH_MAX.
*/
if (pszProcName)
{
{
{
if (hProc)
{
*pszProcName = '\0';
if ( cbRet > 0
fFound = true;
}
}
}
}
else
}
}
return fFound;
}
/**
* Finds a one of the processes in @a papszNames running with user @a pSid and
* returns a duplicate handle to its token.
*
* @returns Success indicator.
* @param papszNames The process candidates, in prioritized order.
* @param pSid The secure identifier of the user.
* @param phToken Where to return the token handle - duplicate,
* caller closes it on success.
*/
{
bool fFound = false;
/*
* On modern systems (W2K+) try the Toolhelp32 API first; this is more stable
* and reliable. Fallback to EnumProcess on NT4.
*/
if (RT_SUCCESS(rc))
{
if (RT_SUCCESS(rc))
if (RT_SUCCESS(rc))
if (RT_SUCCESS(rc))
{
if (hSnap != INVALID_HANDLE_VALUE)
{
{
{
do
{
{
fFound = true;
break;
}
}
#ifdef RT_STRICT
else
{
}
#endif
}
}
else /* hSnap == INVALID_HANDLE_VALUE */
}
}
/* If we couldn't take a process snapshot for some reason or another, fall
back on the NT4 compatible API. */
if (RT_FAILURE(rc))
return fFound;
}
/**
* Logs on a specified user and returns its primary token.
*
* @return int
*
* @param pwszUser User name.
* @param pwszPassword Password.
* @param pwszDomain Domain (not used at the moment).
* @param phToken Pointer to store the logon token.
*/
static int rtProcWinUserLogon(PRTUTF16 pwszUser, PRTUTF16 pwszPassword, PRTUTF16 pwszDomain, HANDLE *phToken)
{
/** @todo Add domain support! */
/*
* Because we have to deal with http://support.microsoft.com/kb/245683
* for NULL domain names when running on NT4 here, pass an empty string if so.
* However, passing FQDNs should work!
*/
? L"" /* NT4 and older. */
: NULL, /* Windows 2000 and up. */
phToken);
if (!fRc)
return rtProcWinMapErrorCodes(GetLastError());
return VINF_SUCCESS;
}
/**
* Logs off a user, specified by the given token.
*
* @param hToken The token (=user) to log off.
*/
{
}
/**
* Creates an environment block out of a handed in Unicode and RTENV block.
* The RTENV block can overwrite entries already present in the Unicode block.
*
* @return IPRT status code.
*
* @param pvBlock Unicode block (array) of environment entries; name=value
* @param hEnv Handle of an existing RTENV block to use.
* @param ppwszBlock Pointer to the final output.
*/
{
if (RT_SUCCESS(rc))
{
while ( pwch
&& RT_SUCCESS(rc))
{
if (*pwch)
{
char *pszEntry;
if (RT_SUCCESS(rc))
{
/* Don't overwrite values which we already have set to a custom value
* specified in hEnv ... */
}
}
if (*pwch)
break;
}
if (RT_SUCCESS(rc))
}
return rc;
}
/**
* Creates the environment block using Userenv.dll.
*
* Builds up the environment block for a specified user (identified by a token),
* whereas hEnv is an additional set of environment variables which overwrite existing
* values of the user profile. ppwszBlock needs to be destroyed after usage
* calling rtProcWinDestoryEnv().
*
* @return IPRT status code.
*
* @param hToken Token of the user to use.
* @param ppwszBlock Pointer to a pointer of the final UTF16 environment block.
*/
{
if (RT_SUCCESS(rc))
{
if (RT_SUCCESS(rc))
{
if (RT_SUCCESS(rc))
{
{
}
else
}
}
}
/* If we don't have the Userenv-API for whatever reason or something with the
* native environment block failed, try to return at least our own environment block. */
/** @todo this probably isn't a great idea if CreateEnvironmentBlock fails. */
if (RT_FAILURE(rc))
return rc;
}
/**
* Builds up the environment block for a specified user (identified by user name, password
* and domain), whereas hEnv is an additional set of environment variables which overwrite
* existing values of the user profile. ppwszBlock needs to be destroyed after usage
* calling rtProcWinDestoryEnv().
*
* @return IPRT status code.
*
* @param pwszUser User name.
* @param pwszPassword Password.
* @param pwszDomain Domain.
* @param ppwszBlock Pointer to a pointer of the final UTF16 environment block.
*/
static int rtProcWinCreateEnvFromAccount(PRTUTF16 pwszUser, PRTUTF16 pwszPassword, PRTUTF16 pwszDomain,
{
if (RT_SUCCESS(rc))
{
}
return rc;
}
/**
* Destroys an environment block formerly created by rtProcWinEnvironmentCreateInternal(),
* rtProcWinCreateEnvFromToken() or rtProcWinCreateEnvFromAccount().
*
* @param ppwszBlock Environment block to destroy.
*/
{
}
/**
* Method \#2.
*/
static int rtProcWinCreateAsUser2(PRTUTF16 pwszUser, PRTUTF16 pwszPassword, PRTUTF16 pwszExec, PRTUTF16 pwszCmdLine,
{
/*
* So if we want to start a process from a service (RTPROC_FLAGS_SERVICE),
* we have to do the following:
* - Check the credentials supplied and get the user SID.
* user. This of course is only possible if that user is logged in (over
* physical console or terminal services).
* use it in order to allow the newly started process to access the user's
* process (but run it without UI).
*
* The following restrictions apply:
* - A process only can show its UI when the user the process should run
* under is logged in (has a desktop).
* - We do not want to display a process of user A run on the desktop
* of user B on multi session systems.
*
* The following rights are needed in order to use LogonUserW and
* CreateProcessAsUserW, so the local policy has to be modified to:
* - SE_TCB_NAME = Act as part of the operating system
* - SE_INCREASE_QUOTA_NAME
*
* We may fail here with ERROR_PRIVILEGE_NOT_HELD.
*/
if (RT_SUCCESS(rc))
{
bool fFound = false;
if (fFlags & RTPROC_FLAGS_SERVICE)
{
NULL,
&cbSid,
NULL,
&sidNameUse);
if (!fRc)
dwErr = GetLastError();
if ( !fRc
&& cbSid > 0)
{
PSID pSid = (PSID)RTMemAlloc(cbSid * sizeof(wchar_t)); /** @todo r=bird: What's the relationship between wchar_t and PSID? */
AssertPtrReturn(pSid, VERR_NO_MEMORY); /** @todo r=bird: Leaking token handles when we're out of memory... */
if (cchDomain > 0)
{
AssertPtrReturn(pwszDomain, VERR_NO_MEMORY); /** @todo r=bird: Leaking token handles when we're out of memory... */
}
/* Note: Also supports FQDNs! */
pSid,
&cbSid,
&& IsValidSid(pSid))
{
/* Array of process names we want to look for. */
static const char * const s_papszProcNames[] =
{
#ifdef VBOX /* The explorer entry is a fallback in case GA aren't installed. */
{ "VBoxTray.exe" },
#endif
{ "explorer.exe" },
};
}
else
}
}
else /* !RTPROC_FLAGS_SERVICE */
{
/* Nothing to do here right now. */
}
/** @todo Hmm, this function already is too big! We need to split
* it up into several small parts. */
* continue here. */
{
/*
* If we didn't find a matching VBoxTray, just use the token we got
* above from LogonUserW(). This enables us to at least run processes with
* desktop interaction without UI.
*/
if (RT_SUCCESS(rc))
{
if (RT_SUCCESS(rc))
{
if (RT_SUCCESS(rc))
{
if (!(fFlags & RTPROC_FLAGS_NO_PROFILE))
{
dwErr = GetLastError();
}
{
if (RT_SUCCESS(rc))
{
/*
* Useful KB articles:
*/
NULL, /* pProcessAttributes */
NULL, /* pThreadAttributes */
TRUE, /* fInheritHandles */
NULL, /* pCurrentDirectory */
if (fRc)
else
}
if (!(fFlags & RTPROC_FLAGS_NO_PROFILE))
{
#ifdef RT_STRICT
if (!fRc)
{
AssertMsgFailed(("Unloading user profile failed with error %u (%#x) - Are all handles closed? (dwErr=%u)",
}
#endif
}
}
}
}
} /* Account lookup succeeded? */
}
if ( RT_SUCCESS(rc)
return rc;
}
/**
* Method \#1.
*
* This may fail on too old (NT4) platforms or if the calling process
* is running on a SYSTEM account (like a service, ERROR_ACCESS_DENIED) on newer
* platforms (however, this works on W2K!).
*/
static int rtProcWinCreateAsUser1(PRTUTF16 pwszUser, PRTUTF16 pwszPassword, PRTUTF16 pwszExec, PRTUTF16 pwszCmdLine,
{
if (RT_SUCCESS(rc))
{
if (RT_SUCCESS(rc))
{
hEnv, &pwszzBlock);
if (RT_SUCCESS(rc))
{
NULL, /* lpDomain*/
1 /*LOGON_WITH_PROFILE*/, /* dwLogonFlags */
NULL, /* pCurrentDirectory */
if (!fRc)
}
}
}
return rc;
}
static int rtProcWinCreateAsUser(PRTUTF16 pwszUser, PRTUTF16 pwszPassword, PRTUTF16 pwszExec, PRTUTF16 pwszCmdLine,
{
/*
* If we run as a service CreateProcessWithLogon will fail,
* so don't even try it (because of Local System context).
*/
int rc = VERR_TRY_AGAIN;
if (!(fFlags & RTPROC_FLAGS_SERVICE))
rc = rtProcWinCreateAsUser1(pwszUser, pwszPassword, pwszExec, pwszCmdLine, hEnv, dwCreationFlags, pStartupInfo, pProcInfo, fFlags);
/*
* Did the API call above fail because we're running on a too old OS (NT4) or
* we're running as a Windows service?
*/
if (RT_FAILURE(rc))
rc = rtProcWinCreateAsUser2(pwszUser, pwszPassword, pwszExec, pwszCmdLine, hEnv, dwCreationFlags, pStartupInfo, pProcInfo, fFlags);
return rc;
}
/**
* RTPathTraverseList callback used by RTProcCreateEx to locate the executable.
*/
static DECLCALLBACK(int) rtPathFindExec(char const *pchPath, size_t cchPath, void *pvUser1, void *pvUser2)
{
char *pszRealExec = (char *)pvUser2;
if (RT_FAILURE(rc))
return rc;
if (RTFileExists(pszRealExec))
return VINF_SUCCESS;
return VERR_TRY_AGAIN;
}
RTR3DECL(int) RTProcCreateEx(const char *pszExec, const char * const *papszArgs, RTENV hEnv, uint32_t fFlags,
{
/*
* Input validation
*/
AssertReturn(!(fFlags & ~(RTPROC_FLAGS_DETACHED | RTPROC_FLAGS_HIDDEN | RTPROC_FLAGS_SERVICE | RTPROC_FLAGS_SAME_CONTRACT
/** @todo search the PATH (add flag for this). */
/*
* Initialize the globals.
*/
/*
* Resolve the executable name via the PATH if requested.
*/
char szRealExec[RTPATH_MAX];
if ( (fFlags & RTPROC_FLAGS_SEARCH_PATH)
&& !RTPathHavePath(pszExec)
&& !RTPathExists(pszExec) )
{
/* search */
char *pszPath;
else
if (RT_FAILURE(rc))
}
/*
* Get the file descriptors for the handles we've been passed.
*
* It seems there is no point in trying to convince a child process's CRT
* that any of the standard file handles is non-TEXT. So, we don't...
*/
#if 1 /* The CRT should keep the standard handles up to date. */
#else
#endif
/* If we want to have a hidden process (e.g. not visible to
* to the user) use the STARTUPINFO flags. */
if (fFlags & RTPROC_FLAGS_HIDDEN)
{
}
for (int i = 0; i < 3; i++)
{
if (paHandles[i])
{
{
case RTHANDLETYPE_FILE:
break;
case RTHANDLETYPE_PIPE:
break;
case RTHANDLETYPE_SOCKET:
break;
default:
}
/* Get the inheritability of the handle. */
if (*aphStds[i] != INVALID_HANDLE_VALUE)
{
{
}
}
}
}
/*
* Set the inheritability any handles we're handing the child.
*/
rc = VINF_SUCCESS;
for (int i = 0; i < 3; i++)
if ( (afInhStds[i] != 0xffffffff)
&& !(afInhStds[i] & HANDLE_FLAG_INHERIT))
{
{
}
}
/*
* Create the environment block, command line and convert the executable
* name.
*/
if (RT_SUCCESS(rc))
if (RT_SUCCESS(rc))
{
if (RT_SUCCESS(rc))
{
if (RT_SUCCESS(rc))
{
/*
* Get going...
*/
if (fFlags & RTPROC_FLAGS_DETACHED)
if (fFlags & RTPROC_FLAGS_NO_WINDOW)
/*
* Only use the normal CreateProcess stuff if we have no user name
* and we are not running from a (Windows) service. Otherwise use
* the more advanced version in rtProcWinCreateAsUser().
*/
&& !(fFlags & RTPROC_FLAGS_SERVICE))
{
if (CreateProcessW(pwszExec,
NULL, /* pProcessAttributes */
NULL, /* pThreadAttributes */
TRUE, /* fInheritHandles */
NULL, /* pCurrentDirectory */
&ProcInfo))
rc = VINF_SUCCESS;
else
}
else
{
/*
* Convert the additional parameters and use a helper
* function to do the actual work.
*/
if (RT_SUCCESS(rc))
{
if (RT_SUCCESS(rc))
{
}
}
}
if (RT_SUCCESS(rc))
{
if (phProcess)
{
/*
* Add the process to the child process list so
* RTProcWait can reuse and close the process handle.
*/
}
else
rc = VINF_SUCCESS;
}
}
}
}
/* Undo any handle inherit changes. */
for (int i = 0; i < 3; i++)
if ( (afInhStds[i] != 0xffffffff)
&& !(afInhStds[i] & HANDLE_FLAG_INHERIT))
{
}
return rc;
}
{
AssertReturn(!(fFlags & ~(RTPROCWAIT_FLAGS_BLOCK | RTPROCWAIT_FLAGS_NOBLOCK)), VERR_INVALID_PARAMETER);
/*
* Try find the process among the ones we've spawned, otherwise, attempt
* opening the specified process.
*/
{
{
if (dwErr == ERROR_INVALID_PARAMETER)
return VERR_PROCESS_NOT_FOUND;
return RTErrConvertFromWin32(dwErr);
}
}
/*
* Wait for it to terminate.
*/
while (WaitRc == WAIT_IO_COMPLETION)
switch (WaitRc)
{
/*
* It has terminated.
*/
case WAIT_OBJECT_0:
{
{
/** @todo the exit code can be special statuses. */
if (pProcStatus)
{
}
if (hOpenedProc == NULL)
rc = VINF_SUCCESS;
}
else
break;
}
/*
* It hasn't terminated just yet.
*/
case WAIT_TIMEOUT:
break;
/*
* Something went wrong...
*/
case WAIT_FAILED:
break;
case WAIT_ABANDONED:
AssertFailed();
break;
default:
break;
}
if (hOpenedProc != NULL)
return rc;
}
{
/** @todo this isn't quite right. */
}
{
if (Process == NIL_RTPROCESS)
return VINF_SUCCESS;
/*
* Try find the process among the ones we've spawned, otherwise, attempt
* opening the specified process.
*/
{
}
else
{
{
if (!fRc)
}
}
return rc;
}
{
BOOL fRc = GetProcessAffinityMask(GetCurrentProcess(), &dwProcessAffinityMask, &dwSystemAffinityMask);
return dwProcessAffinityMask;
}