VirtualBox

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

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

Automated rebranding to Oracle copyright/license strings via filemuncher

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