VirtualBox

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

Last change on this file since 27667 was 27667, checked in by vboxsync, 15 years ago

iprt/RTProcCreateEx: restored assertion removed in r58933 and added a few more.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 22.3 KB
Line 
1/* $Id: process-win.cpp 27667 2010-03-24 13:38:19Z vboxsync $ */
2/** @file
3 * IPRT - Process, Windows.
4 */
5
6/*
7 * Copyright (C) 2006-2010 Sun Microsystems, Inc.
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 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
27 * Clara, CA 95054 USA or visit http://www.sun.com if you need
28 * additional information or have any questions.
29 */
30
31
32/*******************************************************************************
33* Header Files *
34*******************************************************************************/
35#define LOG_GROUP RTLOGGROUP_PROCESS
36
37#include <Userenv.h>
38#include <Windows.h>
39#include <process.h>
40#include <errno.h>
41
42#include <iprt/process.h>
43#include "internal/iprt.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/mem.h>
54#include <iprt/once.h>
55#include <iprt/pipe.h>
56#include <iprt/string.h>
57#include <iprt/socket.h>
58
59
60/*******************************************************************************
61* Structures and Typedefs *
62*******************************************************************************/
63typedef WINADVAPI BOOL WINAPI FNCREATEPROCESSWITHLOGON(LPCWSTR,
64 LPCWSTR,
65 LPCWSTR,
66 DWORD,
67 LPCWSTR,
68 LPWSTR,
69 DWORD,
70 LPVOID,
71 LPCWSTR,
72 LPSTARTUPINFOW,
73 LPPROCESS_INFORMATION);
74typedef FNCREATEPROCESSWITHLOGON *PFNCREATEPROCESSWITHLOGON;
75
76
77/*******************************************************************************
78* Global Variables *
79*******************************************************************************/
80/** Init once structure. */
81static RTONCE g_rtProcWinInitOnce = RTONCE_INITIALIZER;
82/** Critical section protecting the process array. */
83static RTCRITSECT g_CritSect;
84/** The number of processes in the array. */
85static uint32_t g_cProcesses;
86/** The current allocation size. */
87static uint32_t g_cProcessesAlloc;
88/** Array containing the live or non-reaped child processes. */
89static struct RTPROCWINENTRY
90{
91 /** The process ID. */
92 ULONG_PTR pid;
93 /** The process handle. */
94 HANDLE hProcess;
95} *g_paProcesses;
96
97
98/**
99 * Clean up the globals.
100 *
101 * @param enmReason Ignored.
102 * @param iStatus Ignored.
103 * @param pvUser Ignored.
104 */
105static DECLCALLBACK(void) rtProcWinTerm(RTTERMREASON enmReason, int32_t iStatus, void *pvUser)
106{
107 NOREF(pvUser); NOREF(iStatus); NOREF(enmReason);
108
109 RTCritSectDelete(&g_CritSect);
110
111 size_t i = g_cProcesses;
112 while (i-- > 0)
113 {
114 CloseHandle(g_paProcesses[i].hProcess);
115 g_paProcesses[i].hProcess = NULL;
116 }
117 RTMemFree(g_paProcesses);
118
119 g_paProcesses = NULL;
120 g_cProcesses = 0;
121 g_cProcessesAlloc = 0;
122}
123
124
125/**
126 * Initialize the globals.
127 *
128 * @returns IPRT status code.
129 * @param pvUser1 Ignored.
130 * @param pvUser2 Ignored.
131 */
132static DECLCALLBACK(int32_t) rtProcWinInitOnce(void *pvUser1, void *pvUser2)
133{
134 NOREF(pvUser1); NOREF(pvUser2);
135
136 g_cProcesses = 0;
137 g_cProcessesAlloc = 0;
138 g_paProcesses = NULL;
139 int rc = RTCritSectInit(&g_CritSect);
140 if (RT_SUCCESS(rc))
141 {
142 /** @todo init once, terminate once - this is a generic thing which should
143 * have some kind of static and simpler setup! */
144 rc = RTTermRegisterCallback(rtProcWinTerm, NULL);
145 if (RT_SUCCESS(rc))
146 return rc;
147 RTCritSectDelete(&g_CritSect);
148 }
149 return rc;
150}
151
152
153/**
154 * Gets the process handle for a process from g_paProcesses.
155 *
156 * @returns Process handle if found, NULL if not.
157 * @param pid The process to remove (pid).
158 */
159static HANDLE rtProcWinFindPid(RTPROCESS pid)
160{
161 HANDLE hProcess = NULL;
162
163 RTCritSectEnter(&g_CritSect);
164 uint32_t i = g_cProcesses;
165 while (i-- > 0)
166 if (g_paProcesses[i].pid == pid)
167 {
168 hProcess = g_paProcesses[i].hProcess;
169 break;
170 }
171 RTCritSectLeave(&g_CritSect);
172
173 return hProcess;
174}
175
176
177/**
178 * Removes a process from g_paProcesses.
179 *
180 * @param pid The process to remove (pid).
181 */
182static void rtProcWinRemovePid(RTPROCESS pid)
183{
184 RTCritSectEnter(&g_CritSect);
185 uint32_t i = g_cProcesses;
186 while (i-- > 0)
187 if (g_paProcesses[i].pid == pid)
188 {
189 g_cProcesses--;
190 uint32_t cToMove = g_cProcesses - i;
191 if (cToMove)
192 memmove(&g_paProcesses[i], &g_paProcesses[i + 1], cToMove * sizeof(g_paProcesses[0]));
193 break;
194 }
195 RTCritSectLeave(&g_CritSect);
196}
197
198
199/**
200 * Adds a process to g_paProcesses.
201 *
202 * @returns IPRT status code.
203 * @param pid The process id.
204 * @param hProcess The process handle.
205 */
206static int rtProcWinAddPid(RTPROCESS pid, HANDLE hProcess)
207{
208 RTCritSectEnter(&g_CritSect);
209
210 uint32_t i = g_cProcesses;
211 if (i >= g_cProcessesAlloc)
212 {
213 void *pvNew = RTMemRealloc(g_paProcesses, (i + 16) * sizeof(g_paProcesses[0]));
214 if (RT_UNLIKELY(!pvNew))
215 {
216 RTCritSectLeave(&g_CritSect);
217 return VERR_NO_MEMORY;
218 }
219 g_paProcesses = (struct RTPROCWINENTRY *)pvNew;
220 g_cProcessesAlloc = i + 16;
221 }
222
223 g_paProcesses[i].pid = pid;
224 g_paProcesses[i].hProcess = hProcess;
225 g_cProcesses = i + 1;
226
227 RTCritSectLeave(&g_CritSect);
228 return VINF_SUCCESS;
229}
230
231
232RTR3DECL(int) RTProcCreate(const char *pszExec, const char * const *papszArgs, RTENV Env, unsigned fFlags, PRTPROCESS pProcess)
233{
234 return RTProcCreateEx(pszExec, papszArgs, Env, fFlags,
235 NULL, NULL, NULL, /* standard handles */
236 NULL /*pszAsUser*/, NULL /* pszPassword*/,
237 pProcess);
238}
239
240
241static int rtProcCreateAsUserHlp(PRTUTF16 pwszUser, PRTUTF16 pwszPassword, PRTUTF16 pwszExec, PRTUTF16 pwszCmdLine,
242 PRTUTF16 pwszzBlock, STARTUPINFOW *pStartupInfo, PROCESS_INFORMATION *pProcInfo)
243{
244 /** @todo On NT4 we need to enable the SeTcbPrivilege to act as part of the operating system. Otherwise
245 * we will get error 1314 (priviledge not held) as a response. */
246
247 /*
248 * The following rights are needed in order to use LogonUserW and
249 * CreateProcessAsUserW, so the local policy has to be modified to:
250 * - SE_TCB_NAME = Act as part of the operating system
251 * - SE_ASSIGNPRIMARYTOKEN_NAME = Create/replace a token object
252 * - SE_INCREASE_QUOTA_NAME
253 *
254 * We may fail here with ERROR_PRIVILEGE_NOT_HELD.
255 */
256 int rc;
257 DWORD dwErr = NO_ERROR;
258 HANDLE hToken = INVALID_HANDLE_VALUE;
259 BOOL fRc = LogonUserW(pwszUser,
260 NULL, /* lpDomain */
261 pwszPassword,
262 LOGON32_LOGON_INTERACTIVE,
263 LOGON32_PROVIDER_DEFAULT,
264 &hToken);
265 if (fRc)
266 {
267 fRc = CreateProcessAsUserW(hToken,
268 pwszExec,
269 pwszCmdLine,
270 NULL, /* pProcessAttributes */
271 NULL, /* pThreadAttributes */
272 TRUE, /* fInheritHandles */
273 CREATE_UNICODE_ENVIRONMENT, /* dwCreationFlags */
274 pwszzBlock,
275 NULL, /* pCurrentDirectory */
276 pStartupInfo,
277 pProcInfo);
278 if (!fRc)
279 dwErr = GetLastError();
280 CloseHandle(hToken);
281 }
282 else
283 dwErr = GetLastError();
284
285#ifndef IPRT_TARGET_NT4
286 /*
287 * If we don't hold enough priviledges to spawn a new process with
288 * different credentials we have to use CreateProcessWithLogonW here. This
289 * API is W2K+ and uses a system service for spawning the process.
290 */
291 /** @todo Use fFlags to either use this feature or just fail. */
292 if (dwErr == ERROR_PRIVILEGE_NOT_HELD)
293 {
294 RTLDRMOD hAdvAPI32;
295 rc = RTLdrLoad("Advapi32.dll", &hAdvAPI32);
296 if (RT_SUCCESS(rc))
297 {
298 /* This may fail on too old (NT4) platforms. */
299 PFNCREATEPROCESSWITHLOGON pfnCreateProcessWithLogonW;
300 rc = RTLdrGetSymbol(hAdvAPI32, "CreateProcessWithLogonW", (void**)&pfnCreateProcessWithLogonW);
301 if (RT_SUCCESS(rc))
302 {
303 fRc = pfnCreateProcessWithLogonW(pwszUser,
304 NULL, /* lpDomain*/
305 pwszPassword,
306 1 /*LOGON_WITH_PROFILE*/, /* dwLogonFlags */
307 pwszExec,
308 pwszCmdLine,
309 CREATE_UNICODE_ENVIRONMENT, /* dwCreationFlags */
310 pwszzBlock,
311 NULL, /* pCurrentDirectory */
312 pStartupInfo,
313 pProcInfo);
314 if (fRc)
315 dwErr = NO_ERROR;
316 else
317 dwErr = GetLastError();
318 }
319 RTLdrClose(hAdvAPI32);
320 }
321 }
322#endif
323
324 if (dwErr != NO_ERROR)
325 rc = RTErrConvertFromWin32(dwErr);
326 else
327 rc = VINF_SUCCESS;
328 return rc;
329}
330
331RTR3DECL(int) RTProcCreateEx(const char *pszExec, const char * const *papszArgs, RTENV hEnv, uint32_t fFlags,
332 PCRTHANDLE phStdIn, PCRTHANDLE phStdOut, PCRTHANDLE phStdErr, const char *pszAsUser,
333 const char *pszPassword, PRTPROCESS phProcess)
334{
335 /*
336 * Input validation
337 */
338 AssertPtrReturn(pszExec, VERR_INVALID_POINTER);
339 AssertReturn(*pszExec, VERR_INVALID_PARAMETER);
340 AssertReturn(!(fFlags & ~RTPROC_FLAGS_DAEMONIZE), VERR_INVALID_PARAMETER);
341 AssertReturn(hEnv != NIL_RTENV, VERR_INVALID_PARAMETER);
342 AssertPtrReturn(papszArgs, VERR_INVALID_PARAMETER);
343 AssertPtrNullReturn(pszAsUser, VERR_INVALID_POINTER);
344 AssertReturn(!pszAsUser || *pszAsUser, VERR_INVALID_PARAMETER);
345 AssertReturn(!pszPassword || pszAsUser, VERR_INVALID_PARAMETER);
346 AssertPtrNullReturn(pszPassword, VERR_INVALID_POINTER);
347 /** @todo search the PATH (add flag for this). */
348
349 /*
350 * Initialize the globals.
351 */
352 int rc = RTOnce(&g_rtProcWinInitOnce, rtProcWinInitOnce, NULL, NULL);
353 AssertRCReturn(rc, rc);
354
355 /*
356 * Get the file descriptors for the handles we've been passed.
357 *
358 * It seems there is no point in trying to convince a child process's CRT
359 * that any of the standard file handles is non-TEXT. So, we don't...
360 */
361 STARTUPINFOW StartupInfo;
362 RT_ZERO(StartupInfo);
363 StartupInfo.cb = sizeof(StartupInfo);
364 StartupInfo.dwFlags = STARTF_USESTDHANDLES;
365#if 1 /* The CRT should keep the standard handles up to date. */
366 StartupInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
367 StartupInfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
368 StartupInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE);
369#else
370 StartupInfo.hStdInput = _get_osfhandle(0);
371 StartupInfo.hStdOutput = _get_osfhandle(1);
372 StartupInfo.hStdError = _get_osfhandle(2);
373#endif
374 PCRTHANDLE paHandles[3] = { phStdIn, phStdOut, phStdErr };
375 HANDLE *aphStds[3] = { &StartupInfo.hStdInput, &StartupInfo.hStdOutput, &StartupInfo.hStdError };
376 DWORD afInhStds[3] = { 0xffffffff, 0xffffffff, 0xffffffff };
377 for (int i = 0; i < 3; i++)
378 {
379 if (paHandles[i])
380 {
381 AssertPtrReturn(paHandles[i], VERR_INVALID_POINTER);
382 switch (paHandles[i]->enmType)
383 {
384 case RTHANDLETYPE_FILE:
385 *aphStds[i] = paHandles[i]->u.hFile != NIL_RTFILE
386 ? (HANDLE)RTFileToNative(paHandles[i]->u.hFile)
387 : INVALID_HANDLE_VALUE;
388 break;
389
390 case RTHANDLETYPE_PIPE:
391 *aphStds[i] = paHandles[i]->u.hPipe != NIL_RTPIPE
392 ? (HANDLE)RTPipeToNative(paHandles[i]->u.hPipe)
393 : INVALID_HANDLE_VALUE;
394 break;
395
396 case RTHANDLETYPE_SOCKET:
397 *aphStds[i] = paHandles[i]->u.hSocket != NIL_RTSOCKET
398 ? (HANDLE)RTSocketToNative(paHandles[i]->u.hSocket)
399 : INVALID_HANDLE_VALUE;
400 break;
401
402 default:
403 AssertMsgFailedReturn(("%d: %d\n", i, paHandles[i]->enmType), VERR_INVALID_PARAMETER);
404 }
405
406 /* Get the inheritability of the handle. */
407 if (*aphStds[i] != INVALID_HANDLE_VALUE)
408 {
409 if (!GetHandleInformation(*aphStds[i], &afInhStds[i]))
410 {
411 rc = RTErrConvertFromWin32(GetLastError());
412 AssertMsgFailedReturn(("%Rrc %p\n", rc, *aphStds[i]), rc);
413 }
414 }
415 }
416 }
417
418 /*
419 * Set the inheritability any handles we're handing the child.
420 */
421 rc = VINF_SUCCESS;
422 for (int i = 0; i < 3; i++)
423 if ( (afInhStds[i] != 0xffffffff)
424 && !(afInhStds[i] & HANDLE_FLAG_INHERIT))
425 {
426 if (!SetHandleInformation(*aphStds[i], HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT))
427 {
428 rc = RTErrConvertFromWin32(GetLastError());
429 AssertMsgFailedBreak(("%Rrc %p\n", rc, *aphStds[i]));
430 }
431 }
432
433 /*
434 * Create the environment block, command line and convert the executable
435 * name.
436 */
437 PRTUTF16 pwszzBlock;
438 if (RT_SUCCESS(rc))
439 rc = RTEnvQueryUtf16Block(hEnv, &pwszzBlock);
440 if (RT_SUCCESS(rc))
441 {
442 PRTUTF16 pwszCmdLine;
443 rc = RTGetOptArgvToUtf16String(&pwszCmdLine, papszArgs, RTGETOPTARGV_CNV_QUOTE_MS_CRT);
444 if (RT_SUCCESS(rc))
445 {
446 PRTUTF16 pwszExec;
447 rc = RTStrToUtf16(pszExec, &pwszExec);
448 if (RT_SUCCESS(rc))
449 {
450 /*
451 * Get going...
452 */
453 PROCESS_INFORMATION ProcInfo;
454 RT_ZERO(ProcInfo);
455 if (pszAsUser == NULL)
456 {
457 if (CreateProcessW(pwszExec,
458 pwszCmdLine,
459 NULL, /* pProcessAttributes */
460 NULL, /* pThreadAttributes */
461 TRUE, /* fInheritHandles */
462 CREATE_UNICODE_ENVIRONMENT, /* dwCreationFlags */
463 pwszzBlock,
464 NULL, /* pCurrentDirectory */
465 &StartupInfo,
466 &ProcInfo))
467 rc = VINF_SUCCESS;
468 else
469 rc = RTErrConvertFromWin32(GetLastError());
470 }
471 else
472 {
473 /*
474 * Convert the additional parameters and use a helper
475 * function to do the actual work.
476 */
477 PRTUTF16 pwszUser;
478 rc = RTStrToUtf16(pszAsUser, &pwszUser);
479 if (RT_SUCCESS(rc))
480 {
481 PRTUTF16 pwszPassword;
482 rc = RTStrToUtf16(pszPassword ? pszPassword : "", &pwszPassword);
483 if (RT_SUCCESS(rc))
484 {
485 rc = rtProcCreateAsUserHlp(pwszUser, pwszPassword,
486 pwszExec, pwszCmdLine, pwszzBlock,
487 &StartupInfo, &ProcInfo);
488
489 RTUtf16Free(pwszPassword);
490 }
491 RTUtf16Free(pwszUser);
492 }
493 }
494 if (RT_SUCCESS(rc))
495 {
496 CloseHandle(ProcInfo.hThread);
497 if (phProcess)
498 {
499 /*
500 * Add the process to the child process list so
501 * RTProcWait can reuse and close the process handle.
502 */
503 rtProcWinAddPid(ProcInfo.dwProcessId, ProcInfo.hProcess);
504 *phProcess = ProcInfo.dwProcessId;
505 }
506 else
507 CloseHandle(ProcInfo.hProcess);
508 rc = VINF_SUCCESS;
509 }
510 RTUtf16Free(pwszExec);
511 }
512 RTUtf16Free(pwszCmdLine);
513 }
514 RTEnvFreeUtf16Block(pwszzBlock);
515 }
516
517 /* Undo any handle inherit changes. */
518 for (int i = 0; i < 3; i++)
519 if ( (afInhStds[i] != 0xffffffff)
520 && !(afInhStds[i] & HANDLE_FLAG_INHERIT))
521 {
522 if (!SetHandleInformation(*aphStds[i], HANDLE_FLAG_INHERIT, 0))
523 AssertMsgFailed(("%Rrc %p\n", RTErrConvertFromWin32(GetLastError()), *aphStds[i]));
524 }
525
526 return rc;
527}
528
529
530
531RTR3DECL(int) RTProcWait(RTPROCESS Process, unsigned fFlags, PRTPROCSTATUS pProcStatus)
532{
533 AssertReturn(!(fFlags & ~(RTPROCWAIT_FLAGS_BLOCK | RTPROCWAIT_FLAGS_NOBLOCK)), VERR_INVALID_PARAMETER);
534 int rc = RTOnce(&g_rtProcWinInitOnce, rtProcWinInitOnce, NULL, NULL);
535 AssertRCReturn(rc, rc);
536
537 /*
538 * Try find the process among the ones we've spawned, otherwise, attempt
539 * opening the specified process.
540 */
541 HANDLE hProcess = rtProcWinFindPid(Process);
542 if (hProcess == NULL)
543 hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | SYNCHRONIZE, FALSE, Process);
544 if (hProcess != NULL)
545 {
546 /*
547 * Wait for it to terminate.
548 */
549 DWORD Millies = fFlags == RTPROCWAIT_FLAGS_BLOCK ? INFINITE : 0;
550 DWORD WaitRc = WaitForSingleObjectEx(hProcess, Millies, TRUE);
551 while (WaitRc == WAIT_IO_COMPLETION)
552 WaitRc = WaitForSingleObjectEx(hProcess, Millies, TRUE);
553 switch (WaitRc)
554 {
555 /*
556 * It has terminated.
557 */
558 case WAIT_OBJECT_0:
559 {
560 DWORD dwExitCode;
561 if (GetExitCodeProcess(hProcess, &dwExitCode))
562 {
563 /** @todo the exit code can be special statuses. */
564 if (pProcStatus)
565 {
566 pProcStatus->enmReason = RTPROCEXITREASON_NORMAL;
567 pProcStatus->iStatus = (int)dwExitCode;
568 }
569 rtProcWinRemovePid(Process);
570 return VINF_SUCCESS;
571 }
572 break;
573 }
574
575 /*
576 * It hasn't terminated just yet.
577 */
578 case WAIT_TIMEOUT:
579 return VERR_PROCESS_RUNNING;
580
581 /*
582 * Something went wrong...
583 */
584 case WAIT_FAILED:
585 break;
586 case WAIT_ABANDONED:
587 AssertFailed();
588 return VERR_GENERAL_FAILURE;
589 default:
590 AssertMsgFailed(("WaitRc=%RU32\n", WaitRc));
591 return VERR_GENERAL_FAILURE;
592 }
593 }
594 DWORD dwErr = GetLastError();
595 if (dwErr == ERROR_INVALID_PARAMETER)
596 return VERR_PROCESS_NOT_FOUND;
597 return RTErrConvertFromWin32(dwErr);
598}
599
600
601RTR3DECL(int) RTProcWaitNoResume(RTPROCESS Process, unsigned fFlags, PRTPROCSTATUS pProcStatus)
602{
603 /** @todo this isn't quite right. */
604 return RTProcWait(Process, fFlags, pProcStatus);
605}
606
607
608RTR3DECL(int) RTProcTerminate(RTPROCESS Process)
609{
610 int rc = VINF_SUCCESS;
611 HANDLE hProcess = rtProcWinFindPid(Process);
612 if (hProcess != NULL)
613 {
614 if (!TerminateProcess(hProcess, 127))
615 rc = RTErrConvertFromWin32(GetLastError());
616 }
617 else
618 {
619 hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, Process);
620 if (hProcess != NULL)
621 {
622 BOOL fRc = TerminateProcess(hProcess, 127);
623 DWORD dwErr = GetLastError();
624 CloseHandle(hProcess);
625 if (!fRc)
626 rc = RTErrConvertFromWin32(dwErr);
627 }
628 }
629 return rc;
630}
631
632
633RTR3DECL(uint64_t) RTProcGetAffinityMask(void)
634{
635 DWORD_PTR dwProcessAffinityMask = 0xffffffff;
636 DWORD_PTR dwSystemAffinityMask;
637
638 BOOL fRc = GetProcessAffinityMask(GetCurrentProcess(), &dwProcessAffinityMask, &dwSystemAffinityMask);
639 Assert(fRc);
640
641 return dwProcessAffinityMask;
642}
643
Note: See TracBrowser for help on using the repository browser.

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