VirtualBox

source: vbox/trunk/src/VBox/Runtime/r3/win/process-win.cpp@ 50495

Last change on this file since 50495 was 50062, checked in by vboxsync, 11 years ago

IPRT/process-win.cpp: Fixed environment creation for RTProcCreateEx(), regression from r75892.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id Revision
File size: 52.9 KB
Line 
1/* $Id: process-win.cpp 50062 2014-01-13 16:22:56Z vboxsync $ */
2/** @file
3 * IPRT - Process, Windows.
4 */
5
6/*
7 * Copyright (C) 2006-2013 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 *
17 * The contents of this file may alternatively be used under the terms
18 * of the Common Development and Distribution License Version 1.0
19 * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
20 * VirtualBox OSE distribution, in which case the provisions of the
21 * CDDL are applicable instead of those of the GPL.
22 *
23 * You may elect to license modified versions of this file under the
24 * terms and conditions of either the GPL or the CDDL or both.
25 */
26
27
28/*******************************************************************************
29* Header Files *
30*******************************************************************************/
31#define LOG_GROUP RTLOGGROUP_PROCESS
32#include <iprt/asm.h> /* hack */
33
34#include <Userenv.h>
35#include <Windows.h>
36#include <tlhelp32.h>
37#include <process.h>
38#include <errno.h>
39#include <Strsafe.h>
40#include <Lmcons.h>
41
42#include <iprt/process.h>
43#include "internal-r3-win.h"
44
45#include <iprt/assert.h>
46#include <iprt/critsect.h>
47#include <iprt/file.h>
48#include <iprt/err.h>
49#include <iprt/env.h>
50#include <iprt/getopt.h>
51#include <iprt/initterm.h>
52#include <iprt/ldr.h>
53#include <iprt/log.h>
54#include <iprt/mem.h>
55#include <iprt/once.h>
56#include <iprt/path.h>
57#include <iprt/pipe.h>
58#include <iprt/string.h>
59#include <iprt/socket.h>
60
61
62
63/*******************************************************************************
64* Structures and Typedefs *
65*******************************************************************************/
66typedef WINADVAPI BOOL WINAPI FNCREATEPROCESSWITHLOGON(LPCWSTR,
67 LPCWSTR,
68 LPCWSTR,
69 DWORD,
70 LPCWSTR,
71 LPWSTR,
72 DWORD,
73 LPVOID,
74 LPCWSTR,
75 LPSTARTUPINFOW,
76 LPPROCESS_INFORMATION);
77typedef FNCREATEPROCESSWITHLOGON *PFNCREATEPROCESSWITHLOGON;
78
79typedef DWORD WINAPI FNWTSGETACTIVECONSOLESESSIONID();
80typedef FNWTSGETACTIVECONSOLESESSIONID *PFNWTSGETACTIVECONSOLESESSIONID;
81
82typedef HANDLE WINAPI FNCREATETOOLHELP32SNAPSHOT(DWORD, DWORD);
83typedef FNCREATETOOLHELP32SNAPSHOT *PFNCREATETOOLHELP32SNAPSHOT;
84
85typedef BOOL WINAPI FNPROCESS32FIRST(HANDLE, LPPROCESSENTRY32);
86typedef FNPROCESS32FIRST *PFNPROCESS32FIRST;
87
88typedef BOOL WINAPI FNPROCESS32NEXT(HANDLE, LPPROCESSENTRY32);
89typedef FNPROCESS32NEXT *PFNPROCESS32NEXT;
90
91typedef BOOL WINAPI FNENUMPROCESSES(DWORD*, DWORD, DWORD*);
92typedef FNENUMPROCESSES *PFNENUMPROCESSES;
93
94typedef DWORD FNGETMODULEBASENAME(HANDLE, HMODULE, LPTSTR, DWORD);
95typedef FNGETMODULEBASENAME *PFNGETMODULEBASENAME;
96
97typedef BOOL WINAPI FNCREATEENVIRONMENTBLOCK(LPVOID *, HANDLE, BOOL);
98typedef FNCREATEENVIRONMENTBLOCK *PFNCREATEENVIRONMENTBLOCK;
99
100typedef BOOL WINAPI FNPFNDESTROYENVIRONMENTBLOCK(LPVOID);
101typedef FNPFNDESTROYENVIRONMENTBLOCK *PFNPFNDESTROYENVIRONMENTBLOCK;
102
103typedef BOOL WINAPI FNLOADUSERPROFILEW(HANDLE, LPPROFILEINFOW);
104typedef FNLOADUSERPROFILEW *PFNLOADUSERPROFILEW;
105
106typedef BOOL WINAPI FNUNLOADUSERPROFILE(HANDLE, HANDLE);
107typedef FNUNLOADUSERPROFILE *PFNUNLOADUSERPROFILE;
108
109
110/*******************************************************************************
111* Global Variables *
112*******************************************************************************/
113/** Init once structure. */
114static RTONCE g_rtProcWinInitOnce = RTONCE_INITIALIZER;
115/** Critical section protecting the process array. */
116static RTCRITSECT g_CritSect;
117/** The number of processes in the array. */
118static uint32_t g_cProcesses;
119/** The current allocation size. */
120static uint32_t g_cProcessesAlloc;
121/** Array containing the live or non-reaped child processes. */
122static struct RTPROCWINENTRY
123{
124 /** The process ID. */
125 ULONG_PTR pid;
126 /** The process handle. */
127 HANDLE hProcess;
128} *g_paProcesses;
129
130
131/**
132 * Clean up the globals.
133 *
134 * @param enmReason Ignored.
135 * @param iStatus Ignored.
136 * @param pvUser Ignored.
137 */
138static DECLCALLBACK(void) rtProcWinTerm(RTTERMREASON enmReason, int32_t iStatus, void *pvUser)
139{
140 NOREF(pvUser); NOREF(iStatus); NOREF(enmReason);
141
142 RTCritSectDelete(&g_CritSect);
143
144 size_t i = g_cProcesses;
145 while (i-- > 0)
146 {
147 CloseHandle(g_paProcesses[i].hProcess);
148 g_paProcesses[i].hProcess = NULL;
149 }
150 RTMemFree(g_paProcesses);
151
152 g_paProcesses = NULL;
153 g_cProcesses = 0;
154 g_cProcessesAlloc = 0;
155}
156
157
158/**
159 * Initialize the globals.
160 *
161 * @returns IPRT status code.
162 * @param pvUser Ignored.
163 */
164static DECLCALLBACK(int32_t) rtProcWinInitOnce(void *pvUser)
165{
166 NOREF(pvUser);
167
168 g_cProcesses = 0;
169 g_cProcessesAlloc = 0;
170 g_paProcesses = NULL;
171 int rc = RTCritSectInit(&g_CritSect);
172 if (RT_SUCCESS(rc))
173 {
174 /** @todo init once, terminate once - this is a generic thing which should
175 * have some kind of static and simpler setup! */
176 rc = RTTermRegisterCallback(rtProcWinTerm, NULL);
177 if (RT_SUCCESS(rc))
178 return rc;
179 RTCritSectDelete(&g_CritSect);
180 }
181 return rc;
182}
183
184
185/**
186 * Gets the process handle for a process from g_paProcesses.
187 *
188 * @returns Process handle if found, NULL if not.
189 * @param pid The process to remove (pid).
190 */
191static HANDLE rtProcWinFindPid(RTPROCESS pid)
192{
193 HANDLE hProcess = NULL;
194
195 RTCritSectEnter(&g_CritSect);
196 uint32_t i = g_cProcesses;
197 while (i-- > 0)
198 if (g_paProcesses[i].pid == pid)
199 {
200 hProcess = g_paProcesses[i].hProcess;
201 break;
202 }
203 RTCritSectLeave(&g_CritSect);
204
205 return hProcess;
206}
207
208
209/**
210 * Removes a process from g_paProcesses and closes the process handle.
211 *
212 * @param pid The process to remove (pid).
213 */
214static void rtProcWinRemovePid(RTPROCESS pid)
215{
216 RTCritSectEnter(&g_CritSect);
217 uint32_t i = g_cProcesses;
218 while (i-- > 0)
219 if (g_paProcesses[i].pid == pid)
220 {
221 HANDLE hProcess = g_paProcesses[i].hProcess;
222
223 g_cProcesses--;
224 uint32_t cToMove = g_cProcesses - i;
225 if (cToMove)
226 memmove(&g_paProcesses[i], &g_paProcesses[i + 1], cToMove * sizeof(g_paProcesses[0]));
227
228 RTCritSectLeave(&g_CritSect);
229 CloseHandle(hProcess);
230 return;
231 }
232 RTCritSectLeave(&g_CritSect);
233}
234
235
236/**
237 * Adds a process to g_paProcesses.
238 *
239 * @returns IPRT status code.
240 * @param pid The process id.
241 * @param hProcess The process handle.
242 */
243static int rtProcWinAddPid(RTPROCESS pid, HANDLE hProcess)
244{
245 RTCritSectEnter(&g_CritSect);
246
247 uint32_t i = g_cProcesses;
248 if (i >= g_cProcessesAlloc)
249 {
250 void *pvNew = RTMemRealloc(g_paProcesses, (i + 16) * sizeof(g_paProcesses[0]));
251 if (RT_UNLIKELY(!pvNew))
252 {
253 RTCritSectLeave(&g_CritSect);
254 return VERR_NO_MEMORY;
255 }
256 g_paProcesses = (struct RTPROCWINENTRY *)pvNew;
257 g_cProcessesAlloc = i + 16;
258 }
259
260 g_paProcesses[i].pid = pid;
261 g_paProcesses[i].hProcess = hProcess;
262 g_cProcesses = i + 1;
263
264 RTCritSectLeave(&g_CritSect);
265 return VINF_SUCCESS;
266}
267
268
269RTR3DECL(int) RTProcCreate(const char *pszExec, const char * const *papszArgs, RTENV Env, unsigned fFlags, PRTPROCESS pProcess)
270{
271 return RTProcCreateEx(pszExec, papszArgs, Env, fFlags,
272 NULL, NULL, NULL, /* standard handles */
273 NULL /*pszAsUser*/, NULL /* pszPassword*/,
274 pProcess);
275}
276
277
278/**
279 * Map some important or much used Windows error codes
280 * to our error codes.
281 *
282 * @return Mapped IPRT status code.
283 * @param dwError Windows error code to map to IPRT code.
284 */
285static int rtProcWinMapErrorCodes(DWORD dwError)
286{
287 int rc;
288 switch (dwError)
289 {
290 case ERROR_NOACCESS:
291 case ERROR_PRIVILEGE_NOT_HELD:
292 rc = VERR_PERMISSION_DENIED;
293 break;
294
295 case ERROR_PASSWORD_EXPIRED:
296 case ERROR_ACCOUNT_RESTRICTION: /* See: http://support.microsoft.com/kb/303846/ */
297 case ERROR_PASSWORD_RESTRICTION:
298 case ERROR_ACCOUNT_DISABLED: /* See: http://support.microsoft.com/kb/263936 */
299 rc = VERR_ACCOUNT_RESTRICTED;
300 break;
301
302 case ERROR_FILE_CORRUPT:
303 rc = VERR_BAD_EXE_FORMAT;
304 break;
305
306 case ERROR_BAD_DEVICE: /* Can happen when opening funny things like "CON". */
307 rc = VERR_INVALID_NAME;
308 break;
309
310 default:
311 /* Could trigger a debug assertion! */
312 rc = RTErrConvertFromWin32(dwError);
313 break;
314 }
315 return rc;
316}
317
318
319/**
320 * Get the process token of the process indicated by @a dwPID if the @a pSid
321 * matches.
322 *
323 * @returns IPRT status code.
324 *
325 * @param dwPid The process identifier.
326 * @param pSid The secure identifier of the user.
327 * @param phToken Where to return the a duplicate of the process token
328 * handle on success. (The caller closes it.)
329 */
330static int rtProcWinGetProcessTokenHandle(DWORD dwPid, PSID pSid, PHANDLE phToken)
331{
332 AssertPtr(pSid);
333 AssertPtr(phToken);
334
335 int rc;
336 HANDLE hProc = OpenProcess(MAXIMUM_ALLOWED, TRUE, dwPid);
337 if (hProc != NULL)
338 {
339 HANDLE hTokenProc;
340 if (OpenProcessToken(hProc,
341 TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY | TOKEN_DUPLICATE
342 | TOKEN_ASSIGN_PRIMARY | TOKEN_ADJUST_SESSIONID | TOKEN_READ | TOKEN_WRITE,
343 &hTokenProc))
344 {
345 SetLastError(NO_ERROR);
346 DWORD dwSize = 0;
347 BOOL fRc = GetTokenInformation(hTokenProc, TokenUser, NULL, 0, &dwSize);
348 DWORD dwErr = GetLastError();
349 if ( !fRc
350 && dwErr == ERROR_INSUFFICIENT_BUFFER
351 && dwSize > 0)
352 {
353 PTOKEN_USER pTokenUser = (PTOKEN_USER)RTMemTmpAllocZ(dwSize);
354 if (pTokenUser)
355 {
356 if (GetTokenInformation(hTokenProc,
357 TokenUser,
358 pTokenUser,
359 dwSize,
360 &dwSize))
361 {
362 if ( IsValidSid(pTokenUser->User.Sid)
363 && EqualSid(pTokenUser->User.Sid, pSid))
364 {
365 if (DuplicateTokenEx(hTokenProc, MAXIMUM_ALLOWED,
366 NULL, SecurityIdentification, TokenPrimary, phToken))
367 {
368 /*
369 * So we found the process instance which belongs to the user we want to
370 * to run our new process under. This duplicated token will be used for
371 * the actual CreateProcessAsUserW() call then.
372 */
373 rc = VINF_SUCCESS;
374 }
375 else
376 rc = rtProcWinMapErrorCodes(GetLastError());
377 }
378 else
379 rc = VERR_NOT_FOUND;
380 }
381 else
382 rc = rtProcWinMapErrorCodes(GetLastError());
383 RTMemTmpFree(pTokenUser);
384 }
385 else
386 rc = VERR_NO_MEMORY;
387 }
388 else if (fRc || dwErr == NO_ERROR)
389 rc = VERR_IPE_UNEXPECTED_STATUS;
390 else
391 rc = rtProcWinMapErrorCodes(dwErr);
392 CloseHandle(hTokenProc);
393 }
394 else
395 rc = rtProcWinMapErrorCodes(GetLastError());
396 CloseHandle(hProc);
397 }
398 else
399 rc = rtProcWinMapErrorCodes(GetLastError());
400 return rc;
401}
402
403
404/**
405 * Fallback method for rtProcWinFindTokenByProcess that uses the older NT4
406 * PSAPI.DLL API.
407 *
408 * @returns Success indicator.
409 * @param papszNames The process candidates, in prioritized order.
410 * @param pSid The secure identifier of the user.
411 * @param phToken Where to return the token handle - duplicate,
412 * caller closes it on success.
413 *
414 * @remarks NT4 needs a copy of "PSAPI.dll" (redistributed by Microsoft and not
415 * part of the OS) in order to get a lookup. If we don't have this DLL
416 * we are not able to get a token and therefore no UI will be visible.
417 */
418static bool rtProcWinFindTokenByProcessAndPsApi(const char * const *papszNames, PSID pSid, PHANDLE phToken)
419{
420 bool fFound = false;
421
422 /*
423 * Load PSAPI.DLL and resolve the two symbols we need.
424 */
425 PFNGETMODULEBASENAME pfnGetModuleBaseName = (PFNGETMODULEBASENAME)RTLdrGetSystemSymbol("psapi.dll", "GetModuleBaseName");
426 if (!pfnGetModuleBaseName)
427 return false;
428 PFNENUMPROCESSES pfnEnumProcesses = (PFNENUMPROCESSES)RTLdrGetSystemSymbol("psapi.dll", "EnumProcesses");
429 if (!pfnEnumProcesses)
430 return false;
431
432 /*
433 * Get a list of PID. We retry if it looks like there are more PIDs
434 * to be returned than what we supplied buffer space for.
435 */
436 int rc = VINF_SUCCESS;
437 DWORD cbPidsAllocated = 4096;
438 DWORD cbPidsReturned = 0;
439 DWORD *paPids;
440 for (;;)
441 {
442 paPids = (DWORD *)RTMemTmpAlloc(cbPidsAllocated);
443 AssertBreakStmt(paPids, rc = VERR_NO_TMP_MEMORY);
444 if (!pfnEnumProcesses(paPids, cbPidsAllocated, &cbPidsReturned))
445 {
446 rc = RTErrConvertFromWin32(GetLastError());
447 AssertMsgFailedBreak(("%Rrc\n", rc));
448 }
449 if ( cbPidsReturned < cbPidsAllocated
450 || cbPidsAllocated >= _512K)
451 break;
452 RTMemTmpFree(paPids);
453 cbPidsAllocated *= 2;
454 }
455 if (RT_SUCCESS(rc))
456 {
457 /*
458 * Search for the process.
459 *
460 * We ASSUME that the caller won't be specifying any names longer
461 * than RTPATH_MAX.
462 */
463 DWORD cbProcName = RTPATH_MAX;
464 char *pszProcName = (char *)RTMemTmpAlloc(RTPATH_MAX);
465 if (pszProcName)
466 {
467 for (size_t i = 0; papszNames[i] && !fFound; i++)
468 {
469 const DWORD cPids = cbPidsReturned / sizeof(DWORD);
470 for (DWORD iPid = 0; iPid < cPids && !fFound; iPid++)
471 {
472 HANDLE hProc = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, paPids[iPid]);
473 if (hProc)
474 {
475 *pszProcName = '\0';
476 DWORD cbRet = pfnGetModuleBaseName(hProc, 0 /*hModule = exe */, pszProcName, cbProcName);
477 if ( cbRet > 0
478 && _stricmp(pszProcName, papszNames[i]) == 0
479 && RT_SUCCESS(rtProcWinGetProcessTokenHandle(paPids[iPid], pSid, phToken)))
480 fFound = true;
481 CloseHandle(hProc);
482 }
483 }
484 }
485 RTMemTmpFree(pszProcName);
486 }
487 else
488 rc = VERR_NO_TMP_MEMORY;
489 }
490 RTMemTmpFree(paPids);
491
492 return fFound;
493}
494
495
496/**
497 * Finds a one of the processes in @a papszNames running with user @a pSid and
498 * returns a duplicate handle to its token.
499 *
500 * @returns Success indicator.
501 * @param papszNames The process candidates, in prioritized order.
502 * @param pSid The secure identifier of the user.
503 * @param phToken Where to return the token handle - duplicate,
504 * caller closes it on success.
505 */
506static bool rtProcWinFindTokenByProcess(const char * const *papszNames, PSID pSid, PHANDLE phToken)
507{
508 AssertPtr(papszNames);
509 AssertPtr(pSid);
510 AssertPtr(phToken);
511
512 bool fFound = false;
513
514 /*
515 * On modern systems (W2K+) try the Toolhelp32 API first; this is more stable
516 * and reliable. Fallback to EnumProcess on NT4.
517 */
518 PFNCREATETOOLHELP32SNAPSHOT pfnCreateToolhelp32Snapshot;
519 pfnCreateToolhelp32Snapshot = (PFNCREATETOOLHELP32SNAPSHOT)GetProcAddress(g_hModKernel32, "CreateToolhelp32Snapshot");
520 PFNPROCESS32FIRST pfnProcess32First = (PFNPROCESS32FIRST)GetProcAddress(g_hModKernel32, "Process32First");
521 PFNPROCESS32NEXT pfnProcess32Next = (PFNPROCESS32NEXT )GetProcAddress(g_hModKernel32, "Process32Next");
522 bool fFallback = true;
523 if (pfnProcess32Next && pfnProcess32First && pfnCreateToolhelp32Snapshot)
524 {
525 HANDLE hSnap = pfnCreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
526 if (hSnap != INVALID_HANDLE_VALUE)
527 {
528 fFallback = false;
529 for (size_t i = 0; papszNames[i] && !fFound; i++)
530 {
531 PROCESSENTRY32 procEntry;
532 procEntry.dwSize = sizeof(PROCESSENTRY32);
533 if (pfnProcess32First(hSnap, &procEntry))
534 {
535 do
536 {
537 if ( _stricmp(procEntry.szExeFile, papszNames[i]) == 0
538 && RT_SUCCESS(rtProcWinGetProcessTokenHandle(procEntry.th32ProcessID, pSid, phToken)))
539 {
540 fFound = true;
541 break;
542 }
543 } while (pfnProcess32Next(hSnap, &procEntry));
544 }
545#ifdef RT_STRICT
546 else
547 {
548 DWORD dwErr = GetLastError();
549 AssertMsgFailed(("dwErr=%u (%x)\n", dwErr, dwErr));
550 }
551#endif
552 }
553 CloseHandle(hSnap);
554 }
555 }
556
557 /* If we couldn't take a process snapshot for some reason or another, fall
558 back on the NT4 compatible API. */
559 if (fFallback)
560 return rtProcWinFindTokenByProcessAndPsApi(papszNames, pSid, phToken);
561 return fFound;
562}
563
564
565/**
566 * Logs on a specified user and returns its primary token.
567 *
568 * @return int
569 *
570 * @param pwszUser User name.
571 * @param pwszPassword Password.
572 * @param pwszDomain Domain (not used at the moment).
573 * @param phToken Pointer to store the logon token.
574 */
575static int rtProcWinUserLogon(PRTUTF16 pwszUser, PRTUTF16 pwszPassword, PRTUTF16 pwszDomain, HANDLE *phToken)
576{
577 /** @todo Add domain support! */
578 BOOL fRc = LogonUserW(pwszUser,
579 /*
580 * Because we have to deal with http://support.microsoft.com/kb/245683
581 * for NULL domain names when running on NT4 here, pass an empty string if so.
582 * However, passing FQDNs should work!
583 */
584 ((DWORD)(LOBYTE(LOWORD(GetVersion()))) < 5) /* < Windows 2000. */
585 ? L"" /* NT4 and older. */
586 : NULL, /* Windows 2000 and up. */
587 pwszPassword,
588 LOGON32_LOGON_INTERACTIVE,
589 LOGON32_PROVIDER_DEFAULT,
590 phToken);
591 if (!fRc)
592 {
593 DWORD dwErr = GetLastError();
594 int rc = rtProcWinMapErrorCodes(dwErr);
595 if (rc == VERR_UNRESOLVED_ERROR)
596 LogRelFunc(("dwErr=%u (%#x), rc=%Rrc\n", dwErr, dwErr, rc));
597 return rc;
598 }
599 return VINF_SUCCESS;
600}
601
602
603/**
604 * Logs off a user, specified by the given token.
605 *
606 * @param hToken The token (=user) to log off.
607 */
608static void rtProcWinUserLogoff(HANDLE hToken)
609{
610 CloseHandle(hToken);
611}
612
613
614/**
615 * Creates an environment block out of a handed in Unicode and RTENV block.
616 * The RTENV block can overwrite entries already present in the Unicode block.
617 *
618 * @return IPRT status code.
619 *
620 * @param pvBlock Unicode block (array) of environment entries; name=value
621 * @param hEnv Handle of an existing RTENV block to use.
622 * @param ppwszBlock Pointer to the final output.
623 */
624static int rtProcWinEnvironmentCreateInternal(VOID *pvBlock, RTENV hEnv, PRTUTF16 *ppwszBlock)
625{
626 RTENV hEnvTemp;
627 int rc = RTEnvClone(&hEnvTemp, hEnv);
628 if (RT_SUCCESS(rc))
629 {
630 PCRTUTF16 pwch = (PCRTUTF16)pvBlock;
631 while ( pwch
632 && RT_SUCCESS(rc))
633 {
634 if (*pwch)
635 {
636 char *pszEntry;
637 rc = RTUtf16ToUtf8(pwch, &pszEntry);
638 if (RT_SUCCESS(rc))
639 {
640 /* Don't overwrite values which we already have set to a custom value
641 * specified in hEnv ... */
642 if (!RTEnvExistEx(hEnv, pszEntry))
643 rc = RTEnvPutEx(hEnvTemp, pszEntry);
644 RTStrFree(pszEntry);
645 }
646 }
647 else
648 break;
649 pwch += RTUtf16Len(pwch) + 1;
650 if (!*pwch)
651 break;
652 }
653
654 if (RT_SUCCESS(rc))
655 rc = RTEnvQueryUtf16Block(hEnvTemp, ppwszBlock);
656 RTEnvDestroy(hEnvTemp);
657 }
658 return rc;
659}
660
661
662/**
663 * Creates the environment block using Userenv.dll.
664 *
665 * Builds up the environment block for a specified user (identified by a token),
666 * whereas hEnv is an additional set of environment variables which overwrite existing
667 * values of the user profile. ppwszBlock needs to be destroyed after usage
668 * calling rtProcWinDestoryEnv().
669 *
670 * @return IPRT status code.
671 *
672 * @param hToken Token of the user to use.
673 * @param hEnv Own environment block to extend/overwrite the profile's data with.
674 * @param ppwszBlock Pointer to a pointer of the final UTF16 environment block.
675 */
676static int rtProcWinCreateEnvFromToken(HANDLE hToken, RTENV hEnv, PRTUTF16 *ppwszBlock)
677{
678 Assert(hToken);
679 Assert(hEnv != NIL_RTENV);
680
681 RTLDRMOD hUserenv;
682 int rc = RTLdrLoadSystem("Userenv.dll", true /*fNoUnload*/, &hUserenv);
683 if (RT_SUCCESS(rc))
684 {
685 PFNCREATEENVIRONMENTBLOCK pfnCreateEnvironmentBlock;
686 rc = RTLdrGetSymbol(hUserenv, "CreateEnvironmentBlock", (void**)&pfnCreateEnvironmentBlock);
687 if (RT_SUCCESS(rc))
688 {
689 PFNPFNDESTROYENVIRONMENTBLOCK pfnDestroyEnvironmentBlock;
690 rc = RTLdrGetSymbol(hUserenv, "DestroyEnvironmentBlock", (void**)&pfnDestroyEnvironmentBlock);
691 if (RT_SUCCESS(rc))
692 {
693 LPVOID pvEnvBlockProfile = NULL;
694 if (pfnCreateEnvironmentBlock(&pvEnvBlockProfile, hToken, FALSE /* Don't inherit from parent. */))
695 {
696 rc = rtProcWinEnvironmentCreateInternal(pvEnvBlockProfile, hEnv, ppwszBlock);
697 pfnDestroyEnvironmentBlock(pvEnvBlockProfile);
698 }
699 else
700 rc = RTErrConvertFromWin32(GetLastError());
701 }
702 }
703 RTLdrClose(hUserenv);
704 }
705
706 /* If we don't have the Userenv-API for whatever reason or something with the
707 * native environment block failed, try to return at least our own environment block. */
708 /** @todo this probably isn't a great idea if CreateEnvironmentBlock fails. */
709 if (RT_FAILURE(rc))
710 rc = RTEnvQueryUtf16Block(hEnv, ppwszBlock);
711 return rc;
712}
713
714
715/**
716 * Builds up the environment block for a specified user (identified by user name, password
717 * and domain), whereas hEnv is an additional set of environment variables which overwrite
718 * existing values of the user profile. ppwszBlock needs to be destroyed after usage
719 * calling rtProcWinDestoryEnv().
720 *
721 * @return IPRT status code.
722 *
723 * @param pwszUser User name.
724 * @param pwszPassword Password.
725 * @param pwszDomain Domain.
726 * @param hEnv Own environment block to extend/overwrite the profile's data with.
727 * @param ppwszBlock Pointer to a pointer of the final UTF16 environment block.
728 */
729static int rtProcWinCreateEnvFromAccount(PRTUTF16 pwszUser, PRTUTF16 pwszPassword, PRTUTF16 pwszDomain,
730 RTENV hEnv, PRTUTF16 *ppwszBlock)
731{
732 HANDLE hToken;
733 int rc = rtProcWinUserLogon(pwszUser, pwszPassword, pwszDomain, &hToken);
734 if (RT_SUCCESS(rc))
735 {
736 rc = rtProcWinCreateEnvFromToken(hToken, hEnv, ppwszBlock);
737 rtProcWinUserLogoff(hToken);
738 }
739 return rc;
740}
741
742
743/**
744 * Destroys an environment block formerly created by rtProcWinEnvironmentCreateInternal(),
745 * rtProcWinCreateEnvFromToken() or rtProcWinCreateEnvFromAccount().
746 *
747 * @param ppwszBlock Environment block to destroy.
748 */
749static void rtProcWinDestroyEnv(PRTUTF16 ppwszBlock)
750{
751 RTEnvFreeUtf16Block(ppwszBlock);
752}
753
754
755/**
756 * Method \#2.
757 */
758static int rtProcWinCreateAsUser2(PRTUTF16 pwszUser, PRTUTF16 pwszPassword, PRTUTF16 pwszExec, PRTUTF16 pwszCmdLine,
759 RTENV hEnv, DWORD dwCreationFlags,
760 STARTUPINFOW *pStartupInfo, PROCESS_INFORMATION *pProcInfo, uint32_t fFlags)
761{
762 /*
763 * So if we want to start a process from a service (RTPROC_FLAGS_SERVICE),
764 * we have to do the following:
765 * - Check the credentials supplied and get the user SID.
766 * - If valid get the correct Explorer/VBoxTray instance corresponding to that
767 * user. This of course is only possible if that user is logged in (over
768 * physical console or terminal services).
769 * - If we found the user's Explorer/VBoxTray app, use and modify the token to
770 * use it in order to allow the newly started process to access the user's
771 * desktop. If there's no Explorer/VBoxTray app we cannot display the started
772 * process (but run it without UI).
773 *
774 * The following restrictions apply:
775 * - A process only can show its UI when the user the process should run
776 * under is logged in (has a desktop).
777 * - We do not want to display a process of user A run on the desktop
778 * of user B on multi session systems.
779 *
780 * The following rights are needed in order to use LogonUserW and
781 * CreateProcessAsUserW, so the local policy has to be modified to:
782 * - SE_TCB_NAME = Act as part of the operating system
783 * - SE_ASSIGNPRIMARYTOKEN_NAME = Create/replace a token object
784 * - SE_INCREASE_QUOTA_NAME
785 *
786 * We may fail here with ERROR_PRIVILEGE_NOT_HELD.
787 */
788 DWORD dwErr = NO_ERROR;
789 PHANDLE phToken = NULL;
790 HANDLE hTokenLogon = INVALID_HANDLE_VALUE;
791 int rc = rtProcWinUserLogon(pwszUser, pwszPassword, NULL /* Domain */, &hTokenLogon);
792 if (RT_SUCCESS(rc))
793 {
794 DWORD fRc;
795 bool fFound = false;
796 HANDLE hTokenUserDesktop = INVALID_HANDLE_VALUE;
797
798 if (fFlags & RTPROC_FLAGS_SERVICE)
799 {
800 DWORD cbSid = 0; /* Must be zero to query size! */
801 DWORD cchDomain = 0;
802 SID_NAME_USE sidNameUse = SidTypeUser;
803 fRc = LookupAccountNameW(NULL,
804 pwszUser,
805 NULL,
806 &cbSid,
807 NULL,
808 &cchDomain,
809 &sidNameUse);
810 if (!fRc)
811 dwErr = GetLastError();
812 if ( !fRc
813 && dwErr == ERROR_INSUFFICIENT_BUFFER
814 && cbSid > 0)
815 {
816 dwErr = NO_ERROR;
817
818 PSID pSid = (PSID)RTMemAlloc(cbSid * sizeof(wchar_t)); /** @todo r=bird: What's the relationship between wchar_t and PSID? */
819 AssertPtrReturn(pSid, VERR_NO_MEMORY); /** @todo r=bird: Leaking token handles when we're out of memory... */
820
821 PRTUTF16 pwszDomain = NULL;
822 if (cchDomain > 0)
823 {
824 pwszDomain = (PRTUTF16)RTMemAlloc(cchDomain * sizeof(RTUTF16));
825 AssertPtrReturn(pwszDomain, VERR_NO_MEMORY); /** @todo r=bird: Leaking token handles when we're out of memory... */
826 }
827
828 /* Note: Also supports FQDNs! */
829 if ( LookupAccountNameW(NULL, /* lpSystemName */
830 pwszUser,
831 pSid,
832 &cbSid,
833 pwszDomain,
834 &cchDomain,
835 &sidNameUse)
836 && IsValidSid(pSid))
837 {
838 /* Array of process names we want to look for. */
839 static const char * const s_papszProcNames[] =
840 {
841#ifdef VBOX /* The explorer entry is a fallback in case GA aren't installed. */
842 { "VBoxTray.exe" },
843#endif
844 { "explorer.exe" },
845 NULL
846 };
847 fFound = rtProcWinFindTokenByProcess(s_papszProcNames, pSid, &hTokenUserDesktop);
848 }
849 else
850 dwErr = GetLastError(); /* LookupAccountNameW() failed. */
851 RTMemFree(pSid);
852 RTMemFree(pwszDomain);
853 }
854 }
855 else /* !RTPROC_FLAGS_SERVICE */
856 {
857 /* Nothing to do here right now. */
858 }
859
860 /** @todo Hmm, this function already is too big! We need to split
861 * it up into several small parts. */
862
863 /* If we got an error due to account lookup/loading above, don't
864 * continue here. */
865 if (dwErr == NO_ERROR)
866 {
867 /*
868 * If we didn't find a matching VBoxTray, just use the token we got
869 * above from LogonUserW(). This enables us to at least run processes with
870 * desktop interaction without UI.
871 */
872 phToken = fFound ? &hTokenUserDesktop : &hTokenLogon;
873 RTLDRMOD hUserenv;
874 rc = RTLdrLoadSystem("Userenv.dll", true /*fNoUnload*/, &hUserenv);
875 if (RT_SUCCESS(rc))
876 {
877 PFNLOADUSERPROFILEW pfnLoadUserProfileW;
878 rc = RTLdrGetSymbol(hUserenv, "LoadUserProfileW", (void**)&pfnLoadUserProfileW);
879 if (RT_SUCCESS(rc))
880 {
881 PFNUNLOADUSERPROFILE pfnUnloadUserProfile;
882 rc = RTLdrGetSymbol(hUserenv, "UnloadUserProfile", (void**)&pfnUnloadUserProfile);
883 if (RT_SUCCESS(rc))
884 {
885 PROFILEINFOW profileInfo;
886 if (!(fFlags & RTPROC_FLAGS_NO_PROFILE))
887 {
888 RT_ZERO(profileInfo);
889 profileInfo.dwSize = sizeof(profileInfo);
890 profileInfo.lpUserName = pwszUser;
891 profileInfo.dwFlags = PI_NOUI; /* Prevents the display of profile error messages. */
892
893 if (!pfnLoadUserProfileW(*phToken, &profileInfo))
894 dwErr = GetLastError();
895 }
896
897 if (dwErr == NO_ERROR)
898 {
899 PRTUTF16 pwszzBlock;
900 rc = rtProcWinCreateEnvFromToken(*phToken, hEnv, &pwszzBlock);
901 if (RT_SUCCESS(rc))
902 {
903 /*
904 * Useful KB articles:
905 * http://support.microsoft.com/kb/165194/
906 * http://support.microsoft.com/kb/184802/
907 * http://support.microsoft.com/kb/327618/
908 */
909 fRc = CreateProcessAsUserW(*phToken,
910 pwszExec,
911 pwszCmdLine,
912 NULL, /* pProcessAttributes */
913 NULL, /* pThreadAttributes */
914 TRUE, /* fInheritHandles */
915 dwCreationFlags,
916 /** @todo Warn about exceeding 8192 bytes
917 * on XP and up. */
918 pwszzBlock, /* lpEnvironment */
919 NULL, /* pCurrentDirectory */
920 pStartupInfo,
921 pProcInfo);
922 if (fRc)
923 dwErr = NO_ERROR;
924 else
925 dwErr = GetLastError(); /* CreateProcessAsUserW() failed. */
926 rtProcWinDestroyEnv(pwszzBlock);
927 }
928
929 if (!(fFlags & RTPROC_FLAGS_NO_PROFILE))
930 {
931 fRc = pfnUnloadUserProfile(*phToken, profileInfo.hProfile);
932#ifdef RT_STRICT
933 if (!fRc)
934 {
935 DWORD dwErr2 = GetLastError();
936 AssertMsgFailed(("Unloading user profile failed with error %u (%#x) - Are all handles closed? (dwErr=%u)",
937 dwErr2, dwErr2, dwErr));
938 }
939#endif
940 }
941 }
942 }
943 }
944 RTLdrClose(hUserenv);
945 } /* Userenv.dll found/loaded? */
946 } /* Account lookup succeeded? */
947 if (hTokenUserDesktop != INVALID_HANDLE_VALUE)
948 CloseHandle(hTokenUserDesktop);
949 rtProcWinUserLogoff(hTokenLogon);
950 }
951
952 if ( RT_SUCCESS(rc)
953 && dwErr != NO_ERROR)
954 {
955 rc = rtProcWinMapErrorCodes(dwErr);
956 if (rc == VERR_UNRESOLVED_ERROR)
957 LogRelFunc(("dwErr=%u (%#x), rc=%Rrc\n", dwErr, dwErr, rc));
958 }
959 return rc;
960}
961
962
963/**
964 * Method \#1.
965 *
966 * This may fail on too old (NT4) platforms or if the calling process
967 * is running on a SYSTEM account (like a service, ERROR_ACCESS_DENIED) on newer
968 * platforms (however, this works on W2K!).
969 */
970static int rtProcWinCreateAsUser1(PRTUTF16 pwszUser, PRTUTF16 pwszPassword, PRTUTF16 pwszExec, PRTUTF16 pwszCmdLine,
971 RTENV hEnv, DWORD dwCreationFlags,
972 STARTUPINFOW *pStartupInfo, PROCESS_INFORMATION *pProcInfo, uint32_t fFlags)
973{
974 PFNCREATEPROCESSWITHLOGON pfnCreateProcessWithLogonW;
975 pfnCreateProcessWithLogonW = (PFNCREATEPROCESSWITHLOGON)RTLdrGetSystemSymbol("Advapi32.dll", "CreateProcessWithLogonW");
976 if (!pfnCreateProcessWithLogonW)
977 return VERR_SYMBOL_NOT_FOUND;
978
979 PRTUTF16 pwszzBlock;
980 int rc = rtProcWinCreateEnvFromAccount(pwszUser, pwszPassword, NULL /* Domain */,
981 hEnv, &pwszzBlock);
982 if (RT_SUCCESS(rc))
983 {
984 BOOL fRc = pfnCreateProcessWithLogonW(pwszUser,
985 NULL, /* lpDomain*/
986 pwszPassword,
987 1 /*LOGON_WITH_PROFILE*/, /* dwLogonFlags */
988 pwszExec,
989 pwszCmdLine,
990 dwCreationFlags,
991 pwszzBlock,
992 NULL, /* pCurrentDirectory */
993 pStartupInfo,
994 pProcInfo);
995 if (!fRc)
996 {
997 DWORD dwErr = GetLastError();
998 rc = rtProcWinMapErrorCodes(dwErr);
999 if (rc == VERR_UNRESOLVED_ERROR)
1000 LogRelFunc(("pfnCreateProcessWithLogonW (%p) failed: dwErr=%u (%#x), rc=%Rrc\n",
1001 pfnCreateProcessWithLogonW, dwErr, dwErr, rc));
1002 }
1003 rtProcWinDestroyEnv(pwszzBlock);
1004 }
1005 return rc;
1006}
1007
1008
1009static int rtProcWinCreateAsUser(PRTUTF16 pwszUser, PRTUTF16 pwszPassword, PRTUTF16 pwszExec, PRTUTF16 pwszCmdLine,
1010 RTENV hEnv, DWORD dwCreationFlags,
1011 STARTUPINFOW *pStartupInfo, PROCESS_INFORMATION *pProcInfo, uint32_t fFlags)
1012{
1013 /*
1014 * If we run as a service CreateProcessWithLogon will fail,
1015 * so don't even try it (because of Local System context).
1016 */
1017 int rc = VERR_TRY_AGAIN;
1018 if (!(fFlags & RTPROC_FLAGS_SERVICE))
1019 rc = rtProcWinCreateAsUser1(pwszUser, pwszPassword, pwszExec, pwszCmdLine, hEnv, dwCreationFlags, pStartupInfo, pProcInfo, fFlags);
1020
1021 /*
1022 * Did the API call above fail because we're running on a too old OS (NT4) or
1023 * we're running as a Windows service?
1024 */
1025 if (RT_FAILURE(rc))
1026 rc = rtProcWinCreateAsUser2(pwszUser, pwszPassword, pwszExec, pwszCmdLine, hEnv, dwCreationFlags, pStartupInfo, pProcInfo, fFlags);
1027
1028 return rc;
1029}
1030
1031
1032/**
1033 * RTPathTraverseList callback used by RTProcCreateEx to locate the executable.
1034 */
1035static DECLCALLBACK(int) rtPathFindExec(char const *pchPath, size_t cchPath, void *pvUser1, void *pvUser2)
1036{
1037 const char *pszExec = (const char *)pvUser1;
1038 char *pszRealExec = (char *)pvUser2;
1039 int rc = RTPathJoinEx(pszRealExec, RTPATH_MAX, pchPath, cchPath, pszExec, RTSTR_MAX);
1040 if (RT_FAILURE(rc))
1041 return rc;
1042 if (RTFileExists(pszRealExec))
1043 return VINF_SUCCESS;
1044 return VERR_TRY_AGAIN;
1045}
1046
1047
1048RTR3DECL(int) RTProcCreateEx(const char *pszExec, const char * const *papszArgs, RTENV hEnv, uint32_t fFlags,
1049 PCRTHANDLE phStdIn, PCRTHANDLE phStdOut, PCRTHANDLE phStdErr, const char *pszAsUser,
1050 const char *pszPassword, PRTPROCESS phProcess)
1051{
1052 /*
1053 * Input validation
1054 */
1055 AssertPtrReturn(pszExec, VERR_INVALID_POINTER);
1056 AssertReturn(*pszExec, VERR_INVALID_PARAMETER);
1057 AssertReturn(!(fFlags & ~(RTPROC_FLAGS_DETACHED | RTPROC_FLAGS_HIDDEN | RTPROC_FLAGS_SERVICE | RTPROC_FLAGS_SAME_CONTRACT
1058 | RTPROC_FLAGS_NO_PROFILE | RTPROC_FLAGS_NO_WINDOW | RTPROC_FLAGS_SEARCH_PATH)),
1059 VERR_INVALID_PARAMETER);
1060 AssertReturn(!(fFlags & RTPROC_FLAGS_DETACHED) || !phProcess, VERR_INVALID_PARAMETER);
1061 AssertReturn(hEnv != NIL_RTENV, VERR_INVALID_PARAMETER);
1062 AssertPtrReturn(papszArgs, VERR_INVALID_PARAMETER);
1063 AssertPtrNullReturn(pszAsUser, VERR_INVALID_POINTER);
1064 AssertReturn(!pszAsUser || *pszAsUser, VERR_INVALID_PARAMETER);
1065 AssertReturn(!pszPassword || pszAsUser, VERR_INVALID_PARAMETER);
1066 AssertPtrNullReturn(pszPassword, VERR_INVALID_POINTER);
1067
1068 /*
1069 * Initialize the globals.
1070 */
1071 int rc = RTOnce(&g_rtProcWinInitOnce, rtProcWinInitOnce, NULL);
1072 AssertRCReturn(rc, rc);
1073
1074 /*
1075 * Resolve the executable name via the PATH if requested.
1076 */
1077 char szRealExec[RTPATH_MAX];
1078 if ( (fFlags & RTPROC_FLAGS_SEARCH_PATH)
1079 && !RTPathHavePath(pszExec)
1080 && !RTPathExists(pszExec) )
1081 {
1082 /* search */
1083 char *pszPath;
1084 if (RTEnvExistEx(hEnv, "PATH"))
1085 pszPath = RTEnvDupEx(hEnv, "PATH");
1086 else
1087 pszPath = RTEnvDupEx(hEnv, "Path");
1088 rc = RTPathTraverseList(pszPath, ';', rtPathFindExec, (void *)pszExec, &szRealExec[0]);
1089 RTStrFree(pszPath);
1090 if (RT_FAILURE(rc))
1091 return rc == VERR_END_OF_STRING ? VERR_FILE_NOT_FOUND : rc;
1092 pszExec = szRealExec;
1093 }
1094
1095 /*
1096 * Get the file descriptors for the handles we've been passed.
1097 *
1098 * It seems there is no point in trying to convince a child process's CRT
1099 * that any of the standard file handles is non-TEXT. So, we don't...
1100 */
1101 STARTUPINFOW StartupInfo;
1102 RT_ZERO(StartupInfo);
1103 StartupInfo.cb = sizeof(StartupInfo);
1104 StartupInfo.dwFlags = STARTF_USESTDHANDLES;
1105#if 1 /* The CRT should keep the standard handles up to date. */
1106 StartupInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
1107 StartupInfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
1108 StartupInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE);
1109#else
1110 StartupInfo.hStdInput = _get_osfhandle(0);
1111 StartupInfo.hStdOutput = _get_osfhandle(1);
1112 StartupInfo.hStdError = _get_osfhandle(2);
1113#endif
1114 /* If we want to have a hidden process (e.g. not visible to
1115 * to the user) use the STARTUPINFO flags. */
1116 if (fFlags & RTPROC_FLAGS_HIDDEN)
1117 {
1118 StartupInfo.dwFlags |= STARTF_USESHOWWINDOW;
1119 StartupInfo.wShowWindow = SW_HIDE;
1120 }
1121
1122 PCRTHANDLE paHandles[3] = { phStdIn, phStdOut, phStdErr };
1123 HANDLE *aphStds[3] = { &StartupInfo.hStdInput, &StartupInfo.hStdOutput, &StartupInfo.hStdError };
1124 DWORD afInhStds[3] = { 0xffffffff, 0xffffffff, 0xffffffff };
1125 for (int i = 0; i < 3; i++)
1126 {
1127 if (paHandles[i])
1128 {
1129 AssertPtrReturn(paHandles[i], VERR_INVALID_POINTER);
1130 switch (paHandles[i]->enmType)
1131 {
1132 case RTHANDLETYPE_FILE:
1133 *aphStds[i] = paHandles[i]->u.hFile != NIL_RTFILE
1134 ? (HANDLE)RTFileToNative(paHandles[i]->u.hFile)
1135 : INVALID_HANDLE_VALUE;
1136 break;
1137
1138 case RTHANDLETYPE_PIPE:
1139 *aphStds[i] = paHandles[i]->u.hPipe != NIL_RTPIPE
1140 ? (HANDLE)RTPipeToNative(paHandles[i]->u.hPipe)
1141 : INVALID_HANDLE_VALUE;
1142 break;
1143
1144 case RTHANDLETYPE_SOCKET:
1145 *aphStds[i] = paHandles[i]->u.hSocket != NIL_RTSOCKET
1146 ? (HANDLE)RTSocketToNative(paHandles[i]->u.hSocket)
1147 : INVALID_HANDLE_VALUE;
1148 break;
1149
1150 default:
1151 AssertMsgFailedReturn(("%d: %d\n", i, paHandles[i]->enmType), VERR_INVALID_PARAMETER);
1152 }
1153
1154 /* Get the inheritability of the handle. */
1155 if (*aphStds[i] != INVALID_HANDLE_VALUE)
1156 {
1157 if (!GetHandleInformation(*aphStds[i], &afInhStds[i]))
1158 {
1159 rc = RTErrConvertFromWin32(GetLastError());
1160 AssertMsgFailedReturn(("%Rrc %p\n", rc, *aphStds[i]), rc);
1161 }
1162 }
1163 }
1164 }
1165
1166 /*
1167 * Set the inheritability any handles we're handing the child.
1168 */
1169 rc = VINF_SUCCESS;
1170 for (int i = 0; i < 3; i++)
1171 if ( (afInhStds[i] != 0xffffffff)
1172 && !(afInhStds[i] & HANDLE_FLAG_INHERIT))
1173 {
1174 if (!SetHandleInformation(*aphStds[i], HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT))
1175 {
1176 rc = RTErrConvertFromWin32(GetLastError());
1177 AssertMsgFailedBreak(("%Rrc %p\n", rc, *aphStds[i]));
1178 }
1179 }
1180
1181 /*
1182 * Create the environment block, command line and convert the executable
1183 * name.
1184 */
1185 PRTUTF16 pwszzBlock;
1186 if (RT_SUCCESS(rc))
1187 rc = RTEnvQueryUtf16Block(hEnv, &pwszzBlock);
1188 if (RT_SUCCESS(rc))
1189 {
1190 PRTUTF16 pwszCmdLine;
1191 rc = RTGetOptArgvToUtf16String(&pwszCmdLine, papszArgs, RTGETOPTARGV_CNV_QUOTE_MS_CRT);
1192 if (RT_SUCCESS(rc))
1193 {
1194 PRTUTF16 pwszExec;
1195 rc = RTStrToUtf16(pszExec, &pwszExec);
1196 if (RT_SUCCESS(rc))
1197 {
1198 /*
1199 * Get going...
1200 */
1201 PROCESS_INFORMATION ProcInfo;
1202 RT_ZERO(ProcInfo);
1203 DWORD dwCreationFlags = CREATE_UNICODE_ENVIRONMENT;
1204 if (fFlags & RTPROC_FLAGS_DETACHED)
1205 dwCreationFlags |= DETACHED_PROCESS;
1206 if (fFlags & RTPROC_FLAGS_NO_WINDOW)
1207 dwCreationFlags |= CREATE_NO_WINDOW;
1208
1209 /*
1210 * Only use the normal CreateProcess stuff if we have no user name
1211 * and we are not running from a (Windows) service. Otherwise use
1212 * the more advanced version in rtProcWinCreateAsUser().
1213 */
1214 if ( pszAsUser == NULL
1215 && !(fFlags & RTPROC_FLAGS_SERVICE))
1216 {
1217 if (CreateProcessW(pwszExec,
1218 pwszCmdLine,
1219 NULL, /* pProcessAttributes */
1220 NULL, /* pThreadAttributes */
1221 TRUE, /* fInheritHandles */
1222 dwCreationFlags,
1223 pwszzBlock,
1224 NULL, /* pCurrentDirectory */
1225 &StartupInfo,
1226 &ProcInfo))
1227 rc = VINF_SUCCESS;
1228 else
1229 rc = RTErrConvertFromWin32(GetLastError());
1230 }
1231 else
1232 {
1233 /*
1234 * Convert the additional parameters and use a helper
1235 * function to do the actual work.
1236 */
1237 PRTUTF16 pwszUser;
1238 rc = RTStrToUtf16(pszAsUser, &pwszUser);
1239 if (RT_SUCCESS(rc))
1240 {
1241 PRTUTF16 pwszPassword;
1242 rc = RTStrToUtf16(pszPassword ? pszPassword : "", &pwszPassword);
1243 if (RT_SUCCESS(rc))
1244 {
1245 rc = rtProcWinCreateAsUser(pwszUser, pwszPassword,
1246 pwszExec, pwszCmdLine, hEnv, dwCreationFlags,
1247 &StartupInfo, &ProcInfo, fFlags);
1248
1249 RTUtf16Free(pwszPassword);
1250 }
1251 RTUtf16Free(pwszUser);
1252 }
1253 }
1254 if (RT_SUCCESS(rc))
1255 {
1256 CloseHandle(ProcInfo.hThread);
1257 if (phProcess)
1258 {
1259 /*
1260 * Add the process to the child process list so
1261 * RTProcWait can reuse and close the process handle.
1262 */
1263 rtProcWinAddPid(ProcInfo.dwProcessId, ProcInfo.hProcess);
1264 *phProcess = ProcInfo.dwProcessId;
1265 }
1266 else
1267 CloseHandle(ProcInfo.hProcess);
1268 rc = VINF_SUCCESS;
1269 }
1270 RTUtf16Free(pwszExec);
1271 }
1272 RTUtf16Free(pwszCmdLine);
1273 }
1274 RTEnvFreeUtf16Block(pwszzBlock);
1275 }
1276
1277 /* Undo any handle inherit changes. */
1278 for (int i = 0; i < 3; i++)
1279 if ( (afInhStds[i] != 0xffffffff)
1280 && !(afInhStds[i] & HANDLE_FLAG_INHERIT))
1281 {
1282 if (!SetHandleInformation(*aphStds[i], HANDLE_FLAG_INHERIT, 0))
1283 AssertMsgFailed(("%Rrc %p\n", RTErrConvertFromWin32(GetLastError()), *aphStds[i]));
1284 }
1285
1286 return rc;
1287}
1288
1289
1290
1291RTR3DECL(int) RTProcWait(RTPROCESS Process, unsigned fFlags, PRTPROCSTATUS pProcStatus)
1292{
1293 AssertReturn(!(fFlags & ~(RTPROCWAIT_FLAGS_BLOCK | RTPROCWAIT_FLAGS_NOBLOCK)), VERR_INVALID_PARAMETER);
1294 int rc = RTOnce(&g_rtProcWinInitOnce, rtProcWinInitOnce, NULL);
1295 AssertRCReturn(rc, rc);
1296
1297 /*
1298 * Try find the process among the ones we've spawned, otherwise, attempt
1299 * opening the specified process.
1300 */
1301 HANDLE hOpenedProc = NULL;
1302 HANDLE hProcess = rtProcWinFindPid(Process);
1303 if (hProcess == NULL)
1304 {
1305 hProcess = hOpenedProc = OpenProcess(PROCESS_QUERY_INFORMATION | SYNCHRONIZE, FALSE, Process);
1306 if (hProcess == NULL)
1307 {
1308 DWORD dwErr = GetLastError();
1309 if (dwErr == ERROR_INVALID_PARAMETER)
1310 return VERR_PROCESS_NOT_FOUND;
1311 return RTErrConvertFromWin32(dwErr);
1312 }
1313 }
1314
1315 /*
1316 * Wait for it to terminate.
1317 */
1318 DWORD Millies = fFlags == RTPROCWAIT_FLAGS_BLOCK ? INFINITE : 0;
1319 DWORD WaitRc = WaitForSingleObjectEx(hProcess, Millies, TRUE);
1320 while (WaitRc == WAIT_IO_COMPLETION)
1321 WaitRc = WaitForSingleObjectEx(hProcess, Millies, TRUE);
1322 switch (WaitRc)
1323 {
1324 /*
1325 * It has terminated.
1326 */
1327 case WAIT_OBJECT_0:
1328 {
1329 DWORD dwExitCode;
1330 if (GetExitCodeProcess(hProcess, &dwExitCode))
1331 {
1332 /** @todo the exit code can be special statuses. */
1333 if (pProcStatus)
1334 {
1335 pProcStatus->enmReason = RTPROCEXITREASON_NORMAL;
1336 pProcStatus->iStatus = (int)dwExitCode;
1337 }
1338 if (hOpenedProc == NULL)
1339 rtProcWinRemovePid(Process);
1340 rc = VINF_SUCCESS;
1341 }
1342 else
1343 rc = RTErrConvertFromWin32(GetLastError());
1344 break;
1345 }
1346
1347 /*
1348 * It hasn't terminated just yet.
1349 */
1350 case WAIT_TIMEOUT:
1351 rc = VERR_PROCESS_RUNNING;
1352 break;
1353
1354 /*
1355 * Something went wrong...
1356 */
1357 case WAIT_FAILED:
1358 rc = RTErrConvertFromWin32(GetLastError());
1359 break;
1360
1361 case WAIT_ABANDONED:
1362 AssertFailed();
1363 rc = VERR_GENERAL_FAILURE;
1364 break;
1365
1366 default:
1367 AssertMsgFailed(("WaitRc=%RU32\n", WaitRc));
1368 rc = VERR_GENERAL_FAILURE;
1369 break;
1370 }
1371
1372 if (hOpenedProc != NULL)
1373 CloseHandle(hOpenedProc);
1374 return rc;
1375}
1376
1377
1378RTR3DECL(int) RTProcWaitNoResume(RTPROCESS Process, unsigned fFlags, PRTPROCSTATUS pProcStatus)
1379{
1380 /** @todo this isn't quite right. */
1381 return RTProcWait(Process, fFlags, pProcStatus);
1382}
1383
1384
1385RTR3DECL(int) RTProcTerminate(RTPROCESS Process)
1386{
1387 if (Process == NIL_RTPROCESS)
1388 return VINF_SUCCESS;
1389
1390 int rc = RTOnce(&g_rtProcWinInitOnce, rtProcWinInitOnce, NULL);
1391 AssertRCReturn(rc, rc);
1392
1393 /*
1394 * Try find the process among the ones we've spawned, otherwise, attempt
1395 * opening the specified process.
1396 */
1397 HANDLE hProcess = rtProcWinFindPid(Process);
1398 if (hProcess != NULL)
1399 {
1400 if (!TerminateProcess(hProcess, 127))
1401 rc = RTErrConvertFromWin32(GetLastError());
1402 }
1403 else
1404 {
1405 hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, Process);
1406 if (hProcess != NULL)
1407 {
1408 BOOL fRc = TerminateProcess(hProcess, 127);
1409 DWORD dwErr = GetLastError();
1410 CloseHandle(hProcess);
1411 if (!fRc)
1412 rc = RTErrConvertFromWin32(dwErr);
1413 }
1414 }
1415 return rc;
1416}
1417
1418
1419RTR3DECL(uint64_t) RTProcGetAffinityMask(void)
1420{
1421 DWORD_PTR dwProcessAffinityMask = 0xffffffff;
1422 DWORD_PTR dwSystemAffinityMask;
1423
1424 BOOL fRc = GetProcessAffinityMask(GetCurrentProcess(), &dwProcessAffinityMask, &dwSystemAffinityMask);
1425 Assert(fRc);
1426
1427 return dwProcessAffinityMask;
1428}
1429
1430
1431RTR3DECL(int) RTProcQueryUsername(RTPROCESS hProcess, char *pszUser, size_t cbUser,
1432 size_t *pcbUser)
1433{
1434 AssertReturn( (pszUser && cbUser > 0)
1435 || (!pszUser && !cbUser), VERR_INVALID_PARAMETER);
1436
1437 if (hProcess != RTProcSelf())
1438 return VERR_NOT_SUPPORTED;
1439
1440 RTUTF16 awszUserName[UNLEN + 1];
1441 DWORD cchUserName = UNLEN + 1;
1442
1443 if (!GetUserNameW(&awszUserName[0], &cchUserName))
1444 return RTErrConvertFromWin32(GetLastError());
1445
1446 char *pszUserName = NULL;
1447 int rc = RTUtf16ToUtf8(awszUserName, &pszUserName);
1448 if (RT_SUCCESS(rc))
1449 {
1450 size_t cbUserName = strlen(pszUserName) + 1;
1451
1452 if (pcbUser)
1453 *pcbUser = cbUserName;
1454
1455 if (cbUserName > cbUser)
1456 rc = VERR_BUFFER_OVERFLOW;
1457 else
1458 {
1459 memcpy(pszUser, pszUserName, cbUserName);
1460 rc = VINF_SUCCESS;
1461 }
1462
1463 RTStrFree(pszUserName);
1464 }
1465
1466 return rc;
1467}
1468
Note: See TracBrowser for help on using the repository browser.

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette