VirtualBox

source: kBuild/trunk/src/kmk/w32/winchildren.c@ 3161

Last change on this file since 3161 was 3161, checked in by bird, 7 years ago

kmk/win: More child process work, focusing on making kmk_builtin_redirect not requiring the shared lock.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 86.4 KB
Line 
1/* $Id: winchildren.c 3161 2018-03-19 22:40:35Z bird $ */
2/** @file
3 * Child process creation and management for kmk.
4 */
5
6/*
7 * Copyright (c) 2018 knut st. osmundsen <[email protected]>
8 *
9 * This file is part of kBuild.
10 *
11 * kBuild is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 3 of the License, or
14 * (at your option) any later version.
15 *
16 * kBuild is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with kBuild. If not, see <http://www.gnu.org/licenses/>
23 *
24 */
25
26/* No GNU coding style here atm, convert if upstreamed. */
27
28/** @page pg_win_children Windows child process creation and managment
29 *
30 * This new implementation aims at addressing the following:
31 *
32 * 1. Speed up process creation by doing the expensive CreateProcess call
33 * in a worker thread.
34 *
35 * 2. No 64 process limit imposed by WaitForMultipleObjects.
36 *
37 * 3. Better distribute jobs among processor groups.
38 *
39 * 4. Offloading more expensive kmkbuiltin operations to worker threads,
40 * making the main thread focus on managing child processes.
41 *
42 * 5. Output synchronization using reusable pipes [not yet implemented].
43 *
44 *
45 * To be quite honest, the first item (CreateProcess expense) didn't occur to me
46 * at first and was more of a sideeffect discovered along the way. A test
47 * rebuilding IPRT went from 4m52s to 3m19s on a 8 thread system.
48 *
49 * The 2nd and 3rd goals are related to newer build servers that have lots of
50 * CPU threads and various Windows NT (aka NT OS/2 at the time) design choices
51 * made in the late 1980ies.
52 *
53 * WaitForMultipleObjects does not support waiting for more than 64 objects,
54 * unlike poll and select. This is just something everyone ends up having to
55 * work around in the end.
56 *
57 * Affinity masks are uintptr_t sized, so 64-bit hosts can only manage 64
58 * processors and 32-bit only 32. Workaround was introduced with Windows 7
59 * (IIRC) and is called processor groups. The CPU threads are grouped into 1 or
60 * more groups of up to 64 processors. Processes are generally scheduled to a
61 * signle processor group at first, but threads may be changed to be scheduled
62 * on different groups. This code will try distribute children evenly among the
63 * processor groups, using a very simple algorithm (see details in code).
64 *
65 */
66
67
68/*********************************************************************************************************************************
69* Header Files *
70*********************************************************************************************************************************/
71#include "../makeint.h"
72#include "../job.h"
73#include "../debug.h"
74#include "../kmkbuiltin.h"
75#include "winchildren.h"
76
77#include <Windows.h>
78#include <Winternl.h>
79#include <assert.h>
80#include <process.h>
81
82
83/*********************************************************************************************************************************
84* Defined Constants And Macros *
85*********************************************************************************************************************************/
86#define MKWINCHILD_MAX_PATH 1024
87
88/** Checks the UTF-16 environment variable pointed to is the PATH. */
89#define IS_PATH_ENV_VAR(a_cwcVar, a_pwszVar) \
90 ( (a_cwcVar) >= 5 \
91 && (a_pwszVar)[4] == L'=' \
92 && ((a_pwszVar)[0] == L'P' || (a_pwszVar)[0] == L'p') \
93 && ((a_pwszVar)[1] == L'A' || (a_pwszVar)[1] == L'a') \
94 && ((a_pwszVar)[2] == L'T' || (a_pwszVar)[2] == L't') \
95 && ((a_pwszVar)[3] == L'H' || (a_pwszVar)[3] == L'h') )
96
97
98/*********************************************************************************************************************************
99* Structures and Typedefs *
100*********************************************************************************************************************************/
101/**
102 * Child process type.
103 */
104typedef enum WINCHILDTYPE
105{
106 WINCHILDTYPE_INVALID = 0,
107 /** Normal child process. */
108 WINCHILDTYPE_PROCESS,
109#ifdef KMK
110 /** kmkbuiltin command. */
111 WINCHILDTYPE_BUILTIN,
112 /** kSubmit job. */
113 WINCHILDTYPE_SUBMIT,
114 /** kmk_redirect job. */
115 WINCHILDTYPE_REDIRECT,
116#endif
117 /** End of valid child types. */
118 WINCHILDTYPE_END
119} WINCHILDTYPE;
120
121
122/** Pointer to a windows child process. */
123typedef struct WINCHILD *PWINCHILD;
124/**
125 * Windows child process.
126 */
127typedef struct WINCHILD
128{
129 /** Magic / eyecatcher (WINCHILD_MAGIC). */
130 ULONG uMagic;
131 /** Child type. */
132 WINCHILDTYPE enmType;
133 /** Pointer to the next child process. */
134 PWINCHILD pNext;
135 /** The pid for this child. */
136 pid_t pid;
137 /** The make child structure associated with this child. */
138 struct child *pMkChild;
139
140 /** The process exit code. */
141 int iExitCode;
142 /** Kill signal, in case we or someone else killed it. */
143 int iSignal;
144 /** Set if core was dumped. */
145 int fCoreDumped;
146
147 /** Type specific data. */
148 union
149 {
150 /** Data for WINCHILDTYPE_PROCESS. */
151 struct
152 {
153 /** Argument vector (single allocation, strings following array). */
154 char **papszArgs;
155 /** Length of the argument strings. */
156 size_t cbArgsStrings;
157 /** Environment vector. Only a copy if fEnvIsCopy is set. */
158 char **papszEnv;
159 /** If we made a copy of the environment, this is the size of the
160 * strings and terminator string (not in array). This is done to
161 * speed up conversion, since MultiByteToWideChar can handle '\0'. */
162 size_t cbEnvStrings;
163 /** The make shell to use (copy). */
164 char *pszShell;
165 /** Handle to use for standard out. */
166 HANDLE hStdOut;
167 /** Handle to use for standard out. */
168 HANDLE hStdErr;
169 /** Whether to close hStdOut after creating the process. */
170 BOOL fCloseStdOut;
171 /** Whether to close hStdErr after creating the process. */
172 BOOL fCloseStdErr;
173
174 /** Child process handle. */
175 HANDLE hProcess;
176 } Process;
177
178 /** Data for WINCHILDTYPE_SUBMIT. */
179 struct
180 {
181 /** The event we're to wait on (hooked up to a pipe) */
182 HANDLE hEvent;
183 /** Parameter for the cleanup callback. */
184 void *pvSubmitWorker;
185 } Submit;
186
187 /** Data for WINCHILDTYPE_REDIRECT. */
188 struct
189 {
190 /** Child process handle. */
191 HANDLE hProcess;
192 } Redirect;
193 } u;
194
195} WINCHILD;
196/** WINCHILD::uMagic value. */
197#define WINCHILD_MAGIC 0xbabebabeU
198
199
200/**
201 * Data for a windows childcare worker thread.
202 *
203 * We use one worker thread per child, reusing the threads when possible.
204 *
205 * This setup helps avoid the 64-bit handle with the WaitForMultipleObject API.
206 *
207 * It also helps using all CPUs on systems with more than one CPU group
208 * (typically systems with more than 64 CPU threads or/and multiple sockets, or
209 * special configs).
210 *
211 * This helps facilitates using pipes for collecting output child rather
212 * than temporary files. Pipes doesn't involve NTFS and can easily be reused.
213 *
214 * Finally, kBuild specific, this allows running kmkbuiltin_xxxx commands in
215 * threads.
216 */
217typedef struct WINCHILDCAREWORKER
218{
219 /** Magic / eyecatcher (WINCHILDCAREWORKER_MAGIC). */
220 ULONG uMagic;
221 /** The processor group for this worker. */
222 unsigned int iProcessorGroup;
223 /** The thread ID. */
224 unsigned int tid;
225 /** The thread handle. */
226 HANDLE hThread;
227 /** The event the thread is idling on. */
228 HANDLE hEvtIdle;
229 /** Pointer to the current child. */
230 PWINCHILD volatile pCurChild;
231 /** List of children pending execution on this worker.
232 * This is updated atomitically just like g_pTailCompletedChildren. */
233 PWINCHILD volatile pTailTodoChildren;
234 /** TRUE if idle, FALSE if not. */
235 long volatile fIdle;
236} WINCHILDCAREWORKER;
237/** Pointer to a childcare worker thread. */
238typedef WINCHILDCAREWORKER *PWINCHILDCAREWORKER;
239/** WINCHILD::uMagic value. */
240#define WINCHILDCAREWORKER_MAGIC 0xdad0dad0U
241
242
243/*********************************************************************************************************************************
244* Global Variables *
245*********************************************************************************************************************************/
246/** Whether it's initialized or not. */
247static BOOL g_fInitialized = FALSE;
248/** Set when we're shutting down everything. */
249static BOOL volatile g_fShutdown = FALSE;
250/** Event used to wait for children. */
251static HANDLE g_hEvtWaitChildren = INVALID_HANDLE_VALUE;
252/** Number of childcare workers currently in g_papChildCareworkers. */
253static unsigned g_cChildCareworkers = 0;
254/** Maximum number of childcare workers in g_papChildCareworkers. */
255static unsigned g_cChildCareworkersMax = 0;
256/** Pointer to childcare workers. */
257static PWINCHILDCAREWORKER *g_papChildCareworkers = NULL;
258/** The group index for the worker allocator.
259 * This is ever increasing and must be modded by g_cProcessorGroups. */
260static unsigned g_idxProcessorGroupAllocator = 0;
261/** The processor in group index for the worker allocator. */
262static unsigned g_idxProcessorInGroupAllocator = 0;
263/** Number of processor groups in the system. */
264static unsigned g_cProcessorGroups = 1;
265/** Array detailing how many active processors there are in each group. */
266static unsigned const *g_pacProcessorsInGroup = &g_cProcessorGroups;
267/** Kernel32!GetActiveProcessorGroupCount */
268static WORD (WINAPI *g_pfnGetActiveProcessorGroupCount)(VOID);
269/** Kernel32!GetActiveProcessorCount */
270static DWORD (WINAPI *g_pfnGetActiveProcessorCount)(WORD);
271/** Kernel32!SetThreadGroupAffinity */
272static BOOL (WINAPI *g_pfnSetThreadGroupAffinity)(HANDLE, CONST GROUP_AFFINITY *, GROUP_AFFINITY *);
273/** NTDLL!NtQueryInformationProcess */
274static NTSTATUS (NTAPI *g_pfnNtQueryInformationProcess)(HANDLE, PROCESSINFOCLASS, PVOID, ULONG, PULONG);
275/** Set if the windows host is 64-bit. */
276static BOOL g_f64BitHost = (K_ARCH_BITS == 64);
277/** Windows version info.
278 * @note Putting this before the volatile stuff, hoping to keep it in a
279 * different cache line than the static bits above. */
280static OSVERSIONINFOA g_VersionInfo = { sizeof(g_VersionInfo), 4, 0, 1381, VER_PLATFORM_WIN32_NT, {0} };
281
282/** Children that has been completed.
283 * This is updated atomically, pushing completed children in LIFO fashion
284 * (thus 'tail'), then hitting g_hEvtWaitChildren if head. */
285static PWINCHILD volatile g_pTailCompletedChildren = NULL;
286
287/** Number of idle pending children.
288 * This is updated before g_hEvtWaitChildren is signalled. */
289static unsigned volatile g_cPendingChildren = 0;
290
291/** Number of idle childcare worker threads. */
292static unsigned volatile g_cIdleChildcareWorkers = 0;
293/** Index of the last idle child careworker (just a hint). */
294static unsigned volatile g_idxLastChildcareWorker = 0;
295
296/** RW lock for serializing kmkbuiltin_redirect and CreateProcess. */
297static SRWLOCK g_RWLock;
298
299
300
301/**
302 * Initializes the windows child module.
303 *
304 * @param cJobSlots The number of job slots.
305 */
306void MkWinChildInit(unsigned int cJobSlots)
307{
308 HMODULE hmod;
309
310 /*
311 * Figure out how many childcare workers first.
312 */
313 static unsigned int const s_cMaxWorkers = 4096;
314 unsigned cWorkers;
315 if (cJobSlots >= 1 && cJobSlots < s_cMaxWorkers)
316 cWorkers = cJobSlots;
317 else
318 cWorkers = s_cMaxWorkers;
319
320 /*
321 * Allocate the array and the child completed event object.
322 */
323 g_papChildCareworkers = (PWINCHILDCAREWORKER *)xcalloc(cWorkers * sizeof(g_papChildCareworkers[0]));
324 g_cChildCareworkersMax = cWorkers;
325
326 g_hEvtWaitChildren = CreateEvent(NULL, FALSE /*fManualReset*/, FALSE /*fInitialState*/, NULL /*pszName*/);
327 if (!g_hEvtWaitChildren)
328 fatal(NILF, INTSTR_LENGTH, _("MkWinChildInit: CreateEvent failed: %u"), GetLastError());
329
330 /*
331 * NTDLL imports that we need.
332 */
333 hmod = GetModuleHandleA("NTDLL.DLL");
334 *(FARPROC *)&g_pfnNtQueryInformationProcess = GetProcAddress(hmod, "NtQueryInformationProcess");
335 if (!g_pfnNtQueryInformationProcess)
336 fatal(NILF, 0, _("MkWinChildInit: NtQueryInformationProcess not found"));
337
338#if K_ARCH_BITS == 32
339 /*
340 * Initialize g_f64BitHost.
341 */
342 if (!IsWow64Process(GetCurrentProcess(), &g_f64BitHost))
343 fatal(NILF, INTSTR_LENGTH, _("MkWinChildInit: IsWow64Process failed: %u"), GetLastError());
344#elif K_ARCH_BITS == 64
345 assert(g_f64BitHost);
346#else
347# error "K_ARCH_BITS is bad/missing"
348#endif
349
350 /*
351 * Figure out how many processor groups there are.
352 * For that we need to first figure the windows version.
353 */
354 if (!GetVersionExA(&g_VersionInfo))
355 {
356 DWORD uRawVer = GetVersion();
357 g_VersionInfo.dwMajorVersion = uRawVer & 0xff;
358 g_VersionInfo.dwMinorVersion = (uRawVer >> 8) & 0xff;
359 g_VersionInfo.dwBuildNumber = (uRawVer >> 16) & 0x7fff;
360 }
361 if (g_VersionInfo.dwMajorVersion >= 6)
362 {
363 hmod = GetModuleHandleA("KERNEL32.DLL");
364 *(FARPROC *)&g_pfnGetActiveProcessorGroupCount = GetProcAddress(hmod, "GetActiveProcessorGroupCount");
365 *(FARPROC *)&g_pfnGetActiveProcessorCount = GetProcAddress(hmod, "GetActiveProcessorCount");
366 *(FARPROC *)&g_pfnSetThreadGroupAffinity = GetProcAddress(hmod, "SetThreadGroupAffinity");
367 if ( g_pfnSetThreadGroupAffinity
368 && g_pfnGetActiveProcessorCount
369 && g_pfnGetActiveProcessorGroupCount)
370 {
371 unsigned int *pacProcessorsInGroup;
372 unsigned iGroup;
373 g_cProcessorGroups = g_pfnGetActiveProcessorGroupCount();
374 if (g_cProcessorGroups == 0)
375 g_cProcessorGroups = 1;
376
377 pacProcessorsInGroup = (unsigned int *)xmalloc(sizeof(g_pacProcessorsInGroup[0]) * g_cProcessorGroups);
378 g_pacProcessorsInGroup = pacProcessorsInGroup;
379 for (iGroup = 0; iGroup < g_cProcessorGroups; iGroup++)
380 pacProcessorsInGroup[iGroup] = g_pfnGetActiveProcessorCount(iGroup);
381
382 /* We shift the starting group with the make nesting level as part of
383 our very simple distribution strategy. */
384 g_idxProcessorGroupAllocator = makelevel;
385 }
386 else
387 {
388 g_pfnSetThreadGroupAffinity = NULL;
389 g_pfnGetActiveProcessorCount = NULL;
390 g_pfnGetActiveProcessorGroupCount = NULL;
391 }
392 }
393
394 /*
395 * For serializing with standard file handle manipulation (kmkbuiltin_redirect).
396 */
397 InitializeSRWLock(&g_RWLock);
398}
399
400/**
401 * Used by mkWinChildcareWorkerThread() and MkWinChildWait() to get the head
402 * child from a lifo (g_pTailCompletedChildren, pTailTodoChildren).
403 *
404 * @returns Head child.
405 * @param ppTail Pointer to the child variable.
406 * @param pChild Tail child.
407 */
408static PWINCHILD mkWinChildDequeFromLifo(PWINCHILD volatile *ppTail, PWINCHILD pChild)
409{
410 if (pChild->pNext)
411 {
412 PWINCHILD pPrev;
413 do
414 {
415 pPrev = pChild;
416 pChild = pChild->pNext;
417 } while (pChild->pNext);
418 pPrev->pNext = NULL;
419 }
420 else
421 {
422 PWINCHILD const pWantedChild = pChild;
423 pChild = _InterlockedCompareExchangePointer(ppTail, NULL, pWantedChild);
424 if (pChild != pWantedChild)
425 {
426 PWINCHILD pPrev;
427 do
428 {
429 pPrev = pChild;
430 pChild = pChild->pNext;
431 } while (pChild->pNext);
432 pPrev->pNext = NULL;
433 assert(pChild == pWantedChild);
434 }
435 }
436 return pChild;
437}
438
439/**
440 * Duplicates the given UTF-16 string.
441 *
442 * @returns 0
443 * @param pwszSrc The UTF-16 string to duplicate.
444 * @param cwcSrc Length, may include the terminator.
445 * @param ppwszDst Where to return the duplicate.
446 */
447static int mkWinChildDuplicateUtf16String(const WCHAR *pwszSrc, size_t cwcSrc, WCHAR **ppwszDst)
448{
449 size_t cb = sizeof(WCHAR) * cwcSrc;
450 if (cwcSrc > 0 && pwszSrc[cwcSrc - 1] == L'\0')
451 *ppwszDst = (WCHAR *)memcpy(xmalloc(cb), pwszSrc, cb);
452 else
453 {
454 WCHAR *pwszDst = (WCHAR *)xmalloc(cb + sizeof(WCHAR));
455 memcpy(pwszDst, pwszSrc, cb);
456 pwszDst[cwcSrc] = L'\0';
457 *ppwszDst = pwszDst;
458 }
459 return 0;
460}
461
462/**
463 * Commmon worker for waiting on a child process and retrieving the exit code.
464 *
465 * @param pWorker The worker.
466 * @param pChild The child.
467 * @param hProcess The process handle.
468 */
469static void mkWinChildcareWorkerWaitForProcess(PWINCHILDCAREWORKER pWorker, PWINCHILD pChild, HANDLE hProcess)
470{
471 for (;;)
472 {
473 DWORD dwExitCode = -42;
474 DWORD dwStatus = WaitForSingleObject(hProcess, INFINITE);
475 assert(dwStatus != WAIT_FAILED);
476 if (dwStatus == WAIT_OBJECT_0)
477 {
478 DWORD dwExitCode = -42;
479 if (GetExitCodeProcess(hProcess, &dwExitCode))
480 {
481 pChild->iExitCode = (int)dwExitCode;
482 return;
483 }
484 }
485 else if ( dwStatus == WAIT_IO_COMPLETION
486 || dwStatus == WAIT_TIMEOUT /* whatever */)
487 continue; /* however unlikely, these aren't fatal. */
488
489 /* Something failed. */
490 pChild->iExitCode = GetLastError();
491 if (pChild->iExitCode == 0)
492 pChild->iExitCode = -4242;
493 return;
494 }
495}
496
497
498/**
499 * Does the actual process creation given.
500 *
501 * @returns 0 if there is anything to wait on, otherwise non-zero windows error.
502 * @param pWorker The childcare worker.
503 * @param pChild The child.
504 * @param pwszImageName The image path.
505 * @param pwszCommandLine The command line.
506 * @param pwszzEnvironment The enviornment block.
507 */
508static int mkWinChildcareWorkerCreateProcess(PWINCHILDCAREWORKER pWorker, PWINCHILD pChild, WCHAR const *pwszImageName,
509 WCHAR const *pwszCommandLine, WCHAR const *pwszzEnvironment)
510{
511 PROCESS_INFORMATION ProcInfo;
512 STARTUPINFOW StartupInfo;
513 DWORD fFlags = CREATE_UNICODE_ENVIRONMENT;
514 BOOL const fHaveHandles = pChild->u.Process.hStdErr != INVALID_HANDLE_VALUE
515 || pChild->u.Process.hStdOut != INVALID_HANDLE_VALUE;
516 BOOL fRet;
517 DWORD dwErr;
518#ifdef KMK
519 extern int process_priority;
520#endif
521
522 /*
523 * Populate startup info.
524 *
525 * Turns out we can get away without passing TRUE for the inherit handles
526 * parameter to CreateProcess when we're not using STARTF_USESTDHANDLES.
527 * At least on NT, which is all worth caring about at this point + context IMO.
528 *
529 * Not inherting the handles is a good thing because it means we won't
530 * accidentally end up with a pipe handle or such intended for a different
531 * child process, potentially causing the EOF/HUP event to be delayed.
532 *
533 * Since the present handle inhertiance requirements only involves standard
534 * output and error, we'll never set the inherit handles flag and instead
535 * do manual handle duplication and planting.
536 */
537 memset(&StartupInfo, 0, sizeof(StartupInfo));
538 StartupInfo.cb = sizeof(StartupInfo);
539 GetStartupInfoW(&StartupInfo);
540 if (!fHaveHandles)
541 StartupInfo.dwFlags &= ~STARTF_USESTDHANDLES;
542 else
543 {
544 fFlags |= CREATE_SUSPENDED;
545 StartupInfo.dwFlags &= ~STARTF_USESTDHANDLES;
546
547 /* Don't pass CRT inheritance info to the child (from our parent actually). */
548 StartupInfo.cbReserved2 = 0;
549 StartupInfo.lpReserved2 = 0;
550 }
551
552 /*
553 * Flags.
554 */
555#ifdef KMK
556 switch (process_priority)
557 {
558 case 1: fFlags |= CREATE_SUSPENDED | IDLE_PRIORITY_CLASS; break;
559 case 2: fFlags |= CREATE_SUSPENDED | BELOW_NORMAL_PRIORITY_CLASS; break;
560 case 3: fFlags |= CREATE_SUSPENDED | NORMAL_PRIORITY_CLASS; break;
561 case 4: fFlags |= CREATE_SUSPENDED | HIGH_PRIORITY_CLASS; break;
562 case 5: fFlags |= CREATE_SUSPENDED | REALTIME_PRIORITY_CLASS; break;
563 }
564#endif
565 if (g_cProcessorGroups > 1)
566 fFlags |= CREATE_SUSPENDED;
567
568 /*
569 * Try create the process.
570 */
571 DB(DB_JOBS, ("CreateProcessW(%ls, %ls,,, TRUE, %#x...)\n", pwszImageName, pwszCommandLine, fFlags));
572 memset(&ProcInfo, 0, sizeof(ProcInfo));
573 AcquireSRWLockShared(&g_RWLock);
574
575 fRet = CreateProcessW((WCHAR *)pwszImageName, (WCHAR *)pwszCommandLine, NULL /*pProcSecAttr*/, NULL /*pThreadSecAttr*/,
576 FALSE /*fInheritHandles*/, fFlags, (WCHAR *)pwszzEnvironment, NULL /*pwsz*/, &StartupInfo, &ProcInfo);
577 dwErr = GetLastError();
578
579 ReleaseSRWLockShared(&g_RWLock);
580 if (fRet)
581 pChild->u.Process.hProcess = ProcInfo.hProcess;
582 else
583 {
584 fprintf(stderr, "CreateProcess(%ls) failed: %u\n", pwszImageName, dwErr);
585 return pChild->iExitCode = (int)dwErr;
586 }
587
588 /*
589 * If the child is suspended, we've got some adjustment work to be done.
590 */
591 dwErr = ERROR_SUCCESS;
592 if (fFlags & CREATE_SUSPENDED)
593 {
594 /*
595 * First do handle inhertiance as that's the most complicated.
596 */
597 if (fHaveHandles)
598 {
599 /*
600 * Get the PEB address and figure out the child process bit count.
601 */
602 ULONG cbActual1 = 0;
603 PROCESS_BASIC_INFORMATION BasicInfo = { 0, 0, };
604 NTSTATUS rcNt = g_pfnNtQueryInformationProcess(ProcInfo.hProcess, ProcessBasicInformation,
605 &BasicInfo, sizeof(BasicInfo), &cbActual1);
606 if (NT_SUCCESS(rcNt))
607 {
608 /*
609 * Read the user process parameter pointer from the PEB.
610 *
611 * Note! Seems WOW64 processes starts out with a 64-bit PEB and
612 * process parameter block.
613 */
614 BOOL const f32BitPeb = !g_f64BitHost;
615 ULONG const cbChildPtr = f32BitPeb ? 4 : 8;
616 PVOID pvSrcInPeb = (char *)BasicInfo.PebBaseAddress + (f32BitPeb ? 0x10 : 0x20);
617 char * pbDst = 0;
618 SIZE_T cbActual2 = 0;
619 if (ReadProcessMemory(ProcInfo.hProcess, pvSrcInPeb, &pbDst, cbChildPtr, &cbActual2))
620 {
621 /*
622 * Duplicate the handles into the child.
623 */
624 union
625 {
626 ULONGLONG au64Bit[2];
627 ULONG au32Bit[2];
628 } WriteBuf;
629 ULONG idx = 0;
630 HANDLE hChildStdOut = INVALID_HANDLE_VALUE;
631 HANDLE hChildStdErr = INVALID_HANDLE_VALUE;
632
633 pbDst += (f32BitPeb ? 0x1c : 0x28);
634 if (pChild->u.Process.hStdOut != INVALID_HANDLE_VALUE)
635 {
636 if (DuplicateHandle(GetCurrentProcess(), pChild->u.Process.hStdOut, ProcInfo.hProcess,
637 &hChildStdOut, 0, TRUE /*fInheritable*/, DUPLICATE_SAME_ACCESS))
638 {
639 if (f32BitPeb)
640 WriteBuf.au32Bit[idx++] = (DWORD)(uintptr_t)hChildStdOut;
641 else
642 WriteBuf.au64Bit[idx++] = (uintptr_t)hChildStdOut;
643 }
644 else
645 {
646 dwErr = GetLastError();
647 fprintf(stderr, "Failed to duplicate %p (stdout) into the child: %u\n",
648 pChild->u.Process.hStdOut, dwErr);
649 }
650 }
651 else
652 pbDst += cbChildPtr;
653
654 if (pChild->u.Process.hStdErr != INVALID_HANDLE_VALUE)
655 {
656 if (DuplicateHandle(GetCurrentProcess(), pChild->u.Process.hStdErr, ProcInfo.hProcess,
657 &hChildStdErr, 0, TRUE /*fInheritable*/, DUPLICATE_SAME_ACCESS))
658 {
659 if (f32BitPeb)
660 WriteBuf.au32Bit[idx++] = (DWORD)(uintptr_t)hChildStdOut;
661 else
662 WriteBuf.au64Bit[idx++] = (uintptr_t)hChildStdOut;
663 }
664 else
665 {
666 dwErr = GetLastError();
667 fprintf(stderr, "Failed to duplicate %p (stderr) into the child: %u\n",
668 pChild->u.Process.hStdOut, dwErr);
669 }
670 }
671
672 /*
673 * Finally write the handle values into the child.
674 */
675 if ( idx > 0
676 && !WriteProcessMemory(ProcInfo.hProcess, pbDst, &WriteBuf, idx * cbChildPtr, &cbActual2))
677 {
678 dwErr = GetLastError();
679 fprintf(stderr, "Failed to write %p LB %u into child: %u\n", pbDst, idx * cbChildPtr, dwErr);
680 }
681 }
682 else
683 {
684 dwErr = GetLastError();
685 fprintf(stderr, "Failed to read %p LB %u from the child: %u\n", pvSrcInPeb, cbChildPtr, dwErr);
686 }
687 }
688 else
689 {
690 fprintf(stderr, "NtQueryInformationProcess failed on child: %#x\n", rcNt);
691 dwErr = (DWORD)rcNt;
692 }
693 }
694
695 /*
696 * Assign processor group (ignore failure).
697 */
698 if (g_cProcessorGroups > 1)
699 {
700 GROUP_AFFINITY Affinity = { ~(ULONG_PTR)0, pWorker->iProcessorGroup, { 0, 0, 0 } };
701 fRet = g_pfnSetThreadGroupAffinity(ProcInfo.hThread, &Affinity, NULL);
702 assert(fRet);
703 }
704
705#ifdef KMK
706 /*
707 * Set priority (ignore failure).
708 */
709 switch (process_priority)
710 {
711 case 1: fRet = SetThreadPriority(ProcInfo.hThread, THREAD_PRIORITY_IDLE); break;
712 case 2: fRet = SetThreadPriority(ProcInfo.hThread, THREAD_PRIORITY_BELOW_NORMAL); break;
713 case 3: fRet = SetThreadPriority(ProcInfo.hThread, THREAD_PRIORITY_NORMAL); break;
714 case 4: fRet = SetThreadPriority(ProcInfo.hThread, THREAD_PRIORITY_HIGHEST); break;
715 case 5: fRet = SetThreadPriority(ProcInfo.hThread, THREAD_PRIORITY_TIME_CRITICAL); break;
716 default: fRet = TRUE;
717 }
718 assert(fRet);
719#endif
720
721 /*
722 * Resume the thread if the adjustments succeeded, otherwise kill it.
723 */
724 if (dwErr == ERROR_SUCCESS)
725 {
726 fRet = ResumeThread(ProcInfo.hThread);
727 assert(fRet);
728 if (!fRet)
729 {
730 dwErr = GetLastError();
731 fprintf(stderr, "ResumeThread failed on child process: %u\n", dwErr);
732 }
733 }
734 if (dwErr != ERROR_SUCCESS)
735 TerminateProcess(ProcInfo.hProcess, dwErr);
736 }
737
738 /*
739 * Close unnecessary handles.
740 */
741 if ( pChild->u.Process.fCloseStdOut
742 && pChild->u.Process.hStdOut != INVALID_HANDLE_VALUE)
743 {
744 CloseHandle(pChild->u.Process.hStdOut);
745 pChild->u.Process.hStdOut = INVALID_HANDLE_VALUE;
746 pChild->u.Process.fCloseStdOut = FALSE;
747 }
748 if ( pChild->u.Process.fCloseStdErr
749 && pChild->u.Process.hStdErr != INVALID_HANDLE_VALUE)
750 {
751 CloseHandle(pChild->u.Process.hStdErr);
752 pChild->u.Process.hStdErr = INVALID_HANDLE_VALUE;
753 pChild->u.Process.fCloseStdErr = FALSE;
754 }
755
756 CloseHandle(ProcInfo.hThread);
757 return 0;
758}
759
760
761#define MKWCCWCMD_F_CYGWIN_SHELL 1
762#define MKWCCWCMD_F_MKS_SHELL 2
763#define MKWCCWCMD_F_HAVE_SH 4
764#define MKWCCWCMD_F_HAVE_KASH_C 8 /**< kmk_ash -c "..." */
765
766static int mkWinChildcareWorkerConvertCommandline(char **papszArgs, unsigned fFlags, WCHAR **ppwszCommandLine)
767{
768 struct ARGINFO
769 {
770 size_t cchSrc;
771 size_t cwcDst; /**< converted size w/o terminator. */
772 size_t cwcDstExtra : 24; /**< Only set with fSlowly. */
773 size_t fSlowly : 1;
774 size_t fQuoteIt : 1;
775 size_t fEndSlashes : 1; /**< if escapes needed for trailing backslashes. */
776 size_t fExtraSpace : 1; /**< if kash -c "" needs an extra space before the quote. */
777 } *paArgInfo;
778 size_t cArgs;
779 size_t i;
780 size_t cwcNeeded;
781 WCHAR *pwszDst;
782 WCHAR *pwszCmdLine;
783
784 /*
785 * Count them first so we can allocate an info array of the stack.
786 */
787 cArgs = 0;
788 while (papszArgs[cArgs] != NULL)
789 cArgs++;
790 paArgInfo = (struct ARGINFO *)alloca(sizeof(paArgInfo[0]) * cArgs);
791
792 /*
793 * Preprocess them and calculate the exact command line length.
794 */
795 cwcNeeded = 1;
796 for (i = 0; i < cArgs; i++)
797 {
798 char *pszSrc = papszArgs[i];
799 size_t cchSrc = strlen(pszSrc);
800 paArgInfo[i].cchSrc = cchSrc;
801 if (cchSrc == 0)
802 {
803 /* empty needs quoting. */
804 paArgInfo[i].cwcDst = 2;
805 paArgInfo[i].cwcDstExtra = 0;
806 paArgInfo[i].fSlowly = 0;
807 paArgInfo[i].fQuoteIt = 1;
808 paArgInfo[i].fExtraSpace = 0;
809 paArgInfo[i].fEndSlashes = 0;
810 }
811 else
812 {
813 const char *pszSpace = memchr(pszSrc, ' ', cchSrc);
814 const char *pszTab = memchr(pszSrc, '\t', cchSrc);
815 const char *pszDQuote = memchr(pszSrc, '"', cchSrc);
816 const char *pszEscape = memchr(pszSrc, '\\', cchSrc);
817 int cwcDst = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszSrc, cchSrc + 1, NULL, 0);
818 if (cwcDst >= 0)
819 --cwcDst;
820 else
821 {
822 DWORD dwErr = GetLastError();
823 fprintf(stderr, _("MultiByteToWideChar failed to convert argv[%u] (%s): %u\n"), i, pszSrc, dwErr);
824 return dwErr;
825 }
826#if 0
827 if (!pszSpace && !pszTab && !pszDQuote && !pszEscape)
828 {
829 /* no special handling needed. */
830 paArgInfo[i].cwcDst = cwcDst;
831 paArgInfo[i].cwcDstExtra = 0;
832 paArgInfo[i].fSlowly = 0;
833 paArgInfo[i].fQuoteIt = 0;
834 paArgInfo[i].fExtraSpace = 0;
835 paArgInfo[i].fEndSlashes = 0;
836 }
837 else if (!pszDQuote && !pszEscape)
838 {
839 /* Just double quote it. */
840 paArgInfo[i].cwcDst = cwcDst + 2;
841 paArgInfo[i].cwcDstExtra = 0;
842 paArgInfo[i].fSlowly = 0;
843 paArgInfo[i].fQuoteIt = 1;
844 paArgInfo[i].fExtraSpace = 0;
845 paArgInfo[i].fEndSlashes = 0;
846 }
847 else
848#endif
849 {
850 /* Complicated, need to scan the string to figure out what to do. */
851 size_t cwcDstExtra;
852 int cBackslashes;
853 char ch;
854
855 paArgInfo[i].fQuoteIt = 0;
856 paArgInfo[i].fSlowly = 1;
857 paArgInfo[i].fExtraSpace = 0;
858 paArgInfo[i].fEndSlashes = 0;
859
860 cwcDstExtra = 0;
861 cBackslashes = 0;
862 while ((ch = *pszSrc++) != '\0')
863 {
864 switch (ch)
865 {
866 default:
867 cBackslashes = 0;
868 break;
869
870 case '\\':
871 cBackslashes++;
872 break;
873
874 case '"':
875 if (fFlags & (MKWCCWCMD_F_CYGWIN_SHELL | MKWCCWCMD_F_MKS_SHELL))
876 cwcDstExtra += 1;
877 else
878 cwcDstExtra += 1 + cBackslashes;
879 break;
880
881 case ' ':
882 case '\t':
883 if (!paArgInfo[i].fQuoteIt)
884 {
885 paArgInfo[i].fQuoteIt = 1;
886 cwcDstExtra += 2;
887 }
888 cBackslashes = 0;
889 break;
890 }
891 }
892
893 if ( cBackslashes > 0
894 && paArgInfo[i].fQuoteIt
895 && !(fFlags & (MKWCCWCMD_F_CYGWIN_SHELL | MKWCCWCMD_F_MKS_SHELL)))
896 {
897 cwcDstExtra += cBackslashes;
898 paArgInfo[i].fEndSlashes = 1;
899 }
900
901 paArgInfo[i].cwcDst = cwcDst + cwcDstExtra;
902 paArgInfo[i].cwcDstExtra = cwcDstExtra;
903 }
904 }
905
906 if ( (fFlags & MKWCCWCMD_F_HAVE_KASH_C)
907 && paArgInfo[i].fQuoteIt)
908 {
909 paArgInfo[i].fExtraSpace = 1;
910 paArgInfo[i].cwcDst++;
911 paArgInfo[i].cwcDstExtra++;
912 }
913
914 cwcNeeded += (i != 0) + paArgInfo[i].cwcDst;
915 }
916
917 /*
918 * Allocate the result buffer and do the actual conversion.
919 */
920 pwszDst = pwszCmdLine = (WCHAR *)xmalloc(sizeof(WCHAR) * cwcNeeded);
921 for (i = 0; i < cArgs; i++)
922 {
923 char *pszSrc = papszArgs[i];
924 size_t cwcDst = paArgInfo[i].cwcDst;
925
926 if (i != 0)
927 *pwszDst++ = L' ';
928
929 if (paArgInfo[i].fQuoteIt)
930 {
931 *pwszDst++ = L'"';
932 cwcDst -= 2;
933 }
934
935 if (!paArgInfo[i].fSlowly)
936 {
937 int cwcDst2 = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszSrc, paArgInfo[i].cchSrc, pwszDst, cwcDst + 1);
938 assert(cwcDst2 >= 0);
939 pwszDst += cwcDst;
940 }
941 else
942 {
943 /* Do the conversion into the end of the output buffer, then move
944 it up to where it should be char by char. */
945 size_t cBackslashes;
946 size_t cwcLeft = paArgInfo[i].cwcDst - paArgInfo[i].cwcDstExtra;
947 WCHAR volatile *pwchSlowSrc = pwszDst + paArgInfo[i].cwcDstExtra;
948 WCHAR volatile *pwchSlowDst = pwszDst;
949 int cwcDst2 = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszSrc, paArgInfo[i].cchSrc,
950 (WCHAR *)pwchSlowSrc, cwcLeft + 1);
951 assert(cwcDst2 >= 0);
952
953 cBackslashes = 0;
954 while (cwcLeft-- > 0)
955 {
956 WCHAR wcSrc = *pwchSlowSrc++;
957 if (wcSrc != L'\\' && wcSrc != L'"')
958 cBackslashes = 0;
959 else if (wcSrc == L'\\')
960 cBackslashes++;
961 else if ( (fFlags & (MKWCCWCMD_F_CYGWIN_SHELL | MKWCCWCMD_F_HAVE_SH))
962 == (MKWCCWCMD_F_CYGWIN_SHELL | MKWCCWCMD_F_HAVE_SH))
963 *pwchSlowDst++ = L'"'; /* cygwin: '"' instead of '\\', no escaped slashes. */
964 else
965 {
966 if (!(fFlags & (MKWCCWCMD_F_CYGWIN_SHELL | MKWCCWCMD_F_MKS_SHELL)))
967 cBackslashes = 1;
968 while (cBackslashes-- > 0)
969 *pwchSlowDst++ = L'\\';
970 }
971 *pwchSlowDst++ = wcSrc;
972 }
973
974 if (paArgInfo[i].fEndSlashes)
975 while (cBackslashes-- > 0)
976 *pwchSlowDst++ = L'\\';
977
978 pwszDst += cwcDst;
979 assert(pwszDst == (WCHAR *)pwchSlowDst);
980 }
981
982 if (paArgInfo[i].fExtraSpace)
983 *pwszDst++ = L' ';
984 if (paArgInfo[i].fQuoteIt)
985 *pwszDst++ = L'"';
986 }
987 *pwszDst = L'\0';
988 *ppwszCommandLine = pwszCmdLine;
989 return 0;
990}
991
992static int mkWinChildcareWorkerConvertCommandlineWithShell(const WCHAR *pwszShell, char **papszArgs, WCHAR **ppwszCommandLine)
993{
994 return -2;
995}
996
997/**
998 * Searches the environment block for the PATH variable.
999 *
1000 * @returns Pointer to the path in the block or ".".
1001 * @param pwszzEnv The UTF-16 environment block to search.
1002 */
1003static const WCHAR *mkWinChildcareWorkerFindPathValue(const WCHAR *pwszzEnv)
1004{
1005 while (*pwszzEnv)
1006 {
1007 size_t cwcVar = wcslen(pwszzEnv);
1008 if (!IS_PATH_ENV_VAR(cwcVar, pwszzEnv))
1009 pwszzEnv += cwcVar + 1;
1010 else if (cwcVar > 5)
1011 return &pwszzEnv[5];
1012 else
1013 break;
1014 }
1015 return L".";
1016}
1017
1018/**
1019 * Checks if we need to had this executable file to the shell.
1020 *
1021 * @returns TRUE if it's shell fooder, FALSE if we think windows can handle it.
1022 * @param hFile Handle to the file in question
1023 */
1024static BOOL mkWinChildcareWorkerCheckIfNeedShell(HANDLE hFile)
1025{
1026 /*
1027 * Read the first 512 bytes and check for an executable image header.
1028 */
1029 union
1030 {
1031 DWORD dwSignature;
1032 WORD wSignature;
1033 BYTE ab[128];
1034 } uBuf;
1035 DWORD cbRead;
1036 uBuf.dwSignature = 0;
1037 if ( ReadFile(hFile, &uBuf, sizeof(uBuf), &cbRead, NULL /*pOverlapped*/)
1038 && cbRead == sizeof(uBuf))
1039 {
1040 if (uBuf.wSignature == IMAGE_DOS_SIGNATURE)
1041 return FALSE;
1042 if (uBuf.dwSignature == IMAGE_NT_SIGNATURE)
1043 return FALSE;
1044 if ( uBuf.wSignature == IMAGE_OS2_SIGNATURE /* NE */
1045 || uBuf.wSignature == 0x5d4c /* LX */
1046 || uBuf.wSignature == IMAGE_OS2_SIGNATURE_LE /* LE */)
1047 return FALSE;
1048 }
1049 return TRUE;
1050}
1051
1052
1053/**
1054 * Tries to locate the image file, searching the path and maybe falling back on
1055 * the shell in case it knows more (think cygwin with its own view of the file
1056 * system).
1057 *
1058 * This will also check for shell script, falling back on the shell too to
1059 * handle those.
1060 *
1061 * @returns 0 on success, windows error code on failure.
1062 * @param pszArg0 The first argument.
1063 * @param pwszPath The path if mkWinChildcareWorkerConvertEnvironment
1064 * found it.
1065 * @param pwszzEnv The environment block, in case we need to look for
1066 * the path.
1067 * @param pszShell The shell.
1068 * @param ppwszImagePath Where to return the pointer to the image path. This
1069 * could be the shell.
1070 * @param pfNeedShell Where to return shell vs direct execution indicator.
1071 */
1072static int mkWinChildcareWorkerFindImage(char const *pszArg0, WCHAR const *pwszPath, WCHAR const *pwszzEnv,
1073 const char *pszShell, WCHAR **ppwszImagePath, BOOL *pfNeedShell)
1074{
1075 /** @todo Slap a cache on this code. We usually end up executing the same
1076 * stuff over and over again (e.g. compilers, linkers, etc).
1077 * Hitting the file system is slow on windows. */
1078
1079 /*
1080 * Convert pszArg0 to unicode so we can work directly on that.
1081 */
1082 WCHAR wszArg0[MKWINCHILD_MAX_PATH + 4]; /* +4 for painless '.exe' appending */
1083 DWORD dwErr;
1084 size_t cbArg0 = strlen(pszArg0) + 1;
1085 int const cwcArg0 = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszArg0, cbArg0, wszArg0, MKWINCHILD_MAX_PATH);
1086 if (cwcArg0 > 0)
1087 {
1088 HANDLE hFile = INVALID_HANDLE_VALUE;
1089 WCHAR wszPathBuf[MKWINCHILD_MAX_PATH + 4]; /* +4 for painless '.exe' appending */
1090 int cwc;
1091
1092 /*
1093 * If there isn't an .exe suffix, we may have to add one.
1094 * Also we ASSUME that .exe suffixes means no hash bang detection needed.
1095 */
1096 int const fHasExeSuffix = cwcArg0 > CSTRLEN(".exe")
1097 && wszArg0[cwcArg0 - 4] == '.'
1098 && (wszArg0[cwcArg0 - 3] == L'e' || wszArg0[cwcArg0 - 3] == L'E')
1099 && (wszArg0[cwcArg0 - 2] == L'x' || wszArg0[cwcArg0 - 2] == L'X')
1100 && (wszArg0[cwcArg0 - 1] == L'e' || wszArg0[cwcArg0 - 1] == L'E');
1101
1102 /*
1103 * If there isn't any path specified, we need to search the PATH env.var.
1104 */
1105 int const fHasPath = wszArg0[1] == L':'
1106 || wszArg0[0] == L'\\'
1107 || wszArg0[0] == L'/'
1108 || wmemchr(wszArg0, L'/', cwcArg0)
1109 || wmemchr(wszArg0, L'\\', cwcArg0);
1110
1111 /* Before we do anything, flip UNIX slashes to DOS ones. */
1112 WCHAR *pwc = wszArg0;
1113 while ((pwc = wcschr(pwc, L'/')) != NULL)
1114 *pwc++ = L'\\';
1115
1116 /* Don't need to set this all the time... */
1117 *pfNeedShell = FALSE;
1118
1119 /*
1120 * If any kind of path is specified in arg0, we will not search the
1121 * PATH env.var and can limit ourselves to maybe slapping a .exe on to it.
1122 */
1123 if (fHasPath)
1124 {
1125 /*
1126 * If relative to a CWD, turn it into an absolute one.
1127 */
1128 unsigned cwcPath = cwcArg0;
1129 WCHAR *pwszPath = wszArg0;
1130 if ( *pwszPath != L'\\'
1131 && (pwszPath[1] != ':' || pwszPath[2] != L'\\') )
1132 {
1133 DWORD cwcAbsPath = GetFullPathNameW(wszArg0, MKWINCHILD_MAX_PATH, wszPathBuf, NULL);
1134 if (cwcAbsPath > 0)
1135 {
1136 cwcAbsPath = cwcPath + 1; /* include terminator, like MultiByteToWideChar does. */
1137 pwszPath = wszPathBuf;
1138 }
1139 }
1140
1141 /*
1142 * If there is an exectuable path, we only need to check that it exists.
1143 */
1144 if (fHasExeSuffix)
1145 {
1146 DWORD dwAttribs = GetFileAttributesW(pwszPath);
1147 if (dwAttribs != INVALID_FILE_ATTRIBUTES)
1148 return mkWinChildDuplicateUtf16String(pwszPath, cwcPath + 4, ppwszImagePath);
1149 }
1150 else
1151 {
1152 /*
1153 * No suffix, so try open it first to see if it's shell fooder.
1154 * Otherwise, append a .exe suffix and check if it exists.
1155 */
1156 hFile = CreateFileW(pwszPath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
1157 NULL /*pSecAttr*/, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1158 if (hFile != INVALID_HANDLE_VALUE)
1159 {
1160 *pfNeedShell = mkWinChildcareWorkerCheckIfNeedShell(hFile);
1161 CloseHandle(hFile);
1162 if (!*pfNeedShell)
1163 return mkWinChildDuplicateUtf16String(pwszPath, cwcPath, ppwszImagePath);
1164 }
1165 /* Append the .exe suffix and check if it exists. */
1166 else
1167 {
1168 DWORD dwAttribs;
1169 pwszPath[cwcPath - 1] = L'.';
1170 pwszPath[cwcPath ] = L'e';
1171 pwszPath[cwcPath + 1] = L'x';
1172 pwszPath[cwcPath + 2] = L'e';
1173 pwszPath[cwcPath + 3] = L'\0';
1174 dwAttribs = GetFileAttributesW(pwszPath);
1175 if (dwAttribs != INVALID_FILE_ATTRIBUTES)
1176 return mkWinChildDuplicateUtf16String(pwszPath, cwcPath + 4, ppwszImagePath);
1177 }
1178 }
1179 }
1180 /*
1181 * No path, need to search the PATH env.var. for the executable, maybe
1182 * adding an .exe suffix while do so if that is missing.
1183 */
1184 else
1185 {
1186 BOOL fSearchedCwd = FALSE;
1187 if (!pwszPath)
1188 pwszPath = mkWinChildcareWorkerFindPathValue(pwszzEnv);
1189 for (;;)
1190 {
1191 size_t cwcCombined;
1192
1193 /*
1194 * Find the end of the current PATH component.
1195 */
1196 size_t cwcSkip;
1197 WCHAR wcEnd;
1198 size_t cwcComponent = 0;
1199 WCHAR wc;
1200 while ((wc = pwszPath[cwcComponent]) != L'\0')
1201 {
1202 if (wc != ';' && wc != ':')
1203 { /* likely */ }
1204 else if (wc == ';')
1205 break;
1206 else if (cwcComponent != pwszPath[cwcComponent] != L'"' ? 1 : 2)
1207 break;
1208 cwcComponent++;
1209 }
1210 wcEnd = wc;
1211
1212 /* Trim leading spaces and double quotes. */
1213 while ( cwcComponent > 0
1214 && ((wc = *pwszPath) == L'"' || wc == L' ' || wc == L'\t'))
1215 {
1216 pwszPath++;
1217 cwcComponent--;
1218 }
1219 cwcSkip = cwcComponent;
1220
1221 /* Trim trailing spaces & double quotes. */
1222 while ( cwcComponent > 0
1223 && ((wc = pwszPath[cwcComponent - 1]) == L'"' || wc == L' ' || wc == L'\t'))
1224 cwcComponent--;
1225
1226 /*
1227 * Skip empty components. Join the component and the filename, making sure to
1228 * resolve any CWD relative stuff first.
1229 */
1230 cwcCombined = cwcComponent + 1 + cwcArg0;
1231 if (cwcComponent > 0 && cwcCombined <= MKWINCHILD_MAX_PATH)
1232 {
1233 DWORD dwAttribs;
1234
1235 /* Copy the component into wszPathBuf, maybe abspath'ing it. */
1236 DWORD cwcAbsPath = 0;
1237 if ( *pwszPath != L'\\'
1238 && (pwszPath[1] != ':' || pwszPath[2] != L'\\') )
1239 {
1240 WCHAR const wcSaved = pwszPath[cwcCombined];
1241 *(WCHAR *)&pwszPath[cwcCombined] = '\0'; /* Pointing to our converted buffer, so this is okay for now. */
1242 cwcAbsPath = GetFullPathNameW(pwszPath, MKWINCHILD_MAX_PATH, wszPathBuf, NULL);
1243 *(WCHAR *)&pwszPath[cwcCombined] = wcSaved;
1244 if (cwcAbsPath > 0 && cwcAbsPath + 1 + cwcArg0 <= MKWINCHILD_MAX_PATH)
1245 cwcCombined = cwcAbsPath + 1 + cwcArg0;
1246 else
1247 cwcAbsPath = 0;
1248 }
1249 if (cwcAbsPath == 0)
1250 {
1251 memcpy(wszPathBuf, pwszPath, cwcComponent);
1252 cwcAbsPath = cwcComponent;
1253 }
1254
1255 /* Append the filename. */
1256 if ((wc = wszPathBuf[cwcAbsPath - 1]) == L'\\' || wc == L'/' || wc == L':')
1257 {
1258 memcpy(&wszPathBuf[cwcAbsPath], wszArg0, cwcArg0 * sizeof(WCHAR));
1259 cwcCombined--;
1260 }
1261 else
1262 {
1263 wszPathBuf[cwcAbsPath] = L'\\';
1264 memcpy(&wszPathBuf[cwcAbsPath + 1], wszArg0, cwcArg0 * sizeof(WCHAR));
1265 }
1266 assert(wszPathBuf[cwcCombined - 1] == L'\0');
1267
1268 /* DOS slash conversion */
1269 pwc = wszPathBuf;
1270 while ((pwc = wcschr(pwc, L'/')) != NULL)
1271 *pwc++ = L'\\';
1272
1273 /*
1274 * Search with exe suffix first.
1275 */
1276 if (!fHasExeSuffix)
1277 {
1278 wszPathBuf[cwcCombined - 1] = L'.';
1279 wszPathBuf[cwcCombined ] = L'e';
1280 wszPathBuf[cwcCombined + 1] = L'x';
1281 wszPathBuf[cwcCombined + 2] = L'e';
1282 wszPathBuf[cwcCombined + 3] = L'\0';
1283 }
1284 dwAttribs = GetFileAttributesW(wszPathBuf);
1285 if ( dwAttribs != INVALID_FILE_ATTRIBUTES
1286 && !(dwAttribs & FILE_ATTRIBUTE_DIRECTORY))
1287 return mkWinChildDuplicateUtf16String(wszPathBuf, cwcCombined + (fHasExeSuffix ? 0 : 4), ppwszImagePath);
1288 if (!fHasExeSuffix)
1289 {
1290 wszPathBuf[cwcCombined - 1] = L'\0';
1291
1292 /*
1293 * Check if the file exists w/o the added '.exe' suffix. If it does,
1294 * we need to check if we can pass it to CreateProcess or need the shell.
1295 */
1296 hFile = CreateFileW(wszPathBuf, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
1297 NULL /*pSecAttr*/, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1298 if (hFile != INVALID_HANDLE_VALUE)
1299 {
1300 *pfNeedShell = mkWinChildcareWorkerCheckIfNeedShell(hFile);
1301 CloseHandle(hFile);
1302 if (!*pfNeedShell)
1303 return mkWinChildDuplicateUtf16String(wszPathBuf, cwcCombined, ppwszImagePath);
1304 break;
1305 }
1306 }
1307 }
1308
1309 /*
1310 * Advance to the next component.
1311 */
1312 if (wcEnd != '\0')
1313 pwszPath += cwcSkip + 1;
1314 else if (fSearchedCwd)
1315 break;
1316 else
1317 {
1318 fSearchedCwd = TRUE;
1319 pwszPath = L".";
1320 }
1321 }
1322 }
1323
1324 /*
1325 * We need the shell. It will take care of finding/reporting missing
1326 * image files and such.
1327 */
1328 *pfNeedShell = TRUE;
1329 cwc = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszShell, strlen(pszShell), wszPathBuf, MKWINCHILD_MAX_PATH);
1330 if (cwc > 0)
1331 return mkWinChildDuplicateUtf16String(wszPathBuf, cwc, ppwszImagePath);
1332 dwErr = GetLastError();
1333 }
1334 else
1335 {
1336 dwErr = GetLastError();
1337 fprintf(stderr, _("MultiByteToWideChar failed to convert argv[0] (%s): %u\n"), pszArg0, dwErr);
1338 }
1339 return dwErr == ERROR_INSUFFICIENT_BUFFER ? ERROR_FILENAME_EXCED_RANGE : dwErr;
1340}
1341
1342/**
1343 * Creates the environment block.
1344 *
1345 * @returns 0 on success, windows error code on failure.
1346 * @param papszEnv The environment vector to convert.
1347 * @param cbEnvStrings The size of the environment strings, iff they are
1348 * sequential in a block. Otherwise, zero.
1349 * @param ppwszEnv Where to return the pointer to the environment
1350 * block.
1351 * @param ppwszPath Where to return the pointer to the path value within
1352 * the environment block. This will not be set if
1353 * cbEnvStrings is non-zero, more efficient to let
1354 * mkWinChildcareWorkerFindImage() search when needed.
1355 */
1356static int mkWinChildcareWorkerConvertEnvironment(char **papszEnv, size_t cbEnvStrings,
1357 WCHAR **ppwszEnv, WCHAR const **ppwszPath)
1358{
1359 DWORD dwErr;
1360 int cwcRc;
1361 int cwcDst;
1362 WCHAR *pwszzDst;
1363
1364 *ppwszPath = NULL;
1365
1366 /*
1367 * We've got a little optimization here with help from mkWinChildCopyStringArray.
1368 */
1369 if (cbEnvStrings)
1370 {
1371 cwcDst = cbEnvStrings + 32;
1372 pwszzDst = (WCHAR *)xmalloc(cwcDst * sizeof(WCHAR));
1373 cwcRc = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, papszEnv[0], cbEnvStrings, pwszzDst, cwcDst);
1374 if (cwcRc != 0)
1375 {
1376 *ppwszEnv = pwszzDst;
1377 return 0;
1378 }
1379
1380 /* Resize the allocation and try again. */
1381 dwErr = GetLastError();
1382 if (dwErr == ERROR_INSUFFICIENT_BUFFER)
1383 {
1384 cwcRc = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, papszEnv[0], cbEnvStrings, NULL, 0);
1385 if (cwcRc > 0)
1386 cwcDst = cwcRc + 32;
1387 else
1388 cwcDst *= 2;
1389 pwszzDst = (WCHAR *)xrealloc(pwszzDst, cwcDst);
1390 cwcRc = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, papszEnv[0], cbEnvStrings, pwszzDst, cwcDst);
1391 if (cwcRc != 0)
1392 {
1393 *ppwszEnv = pwszzDst;
1394 return 0;
1395 }
1396 dwErr = GetLastError();
1397 }
1398 fprintf(stderr, _("MultiByteToWideChar failed to convert environment block: %u\n"), dwErr);
1399 }
1400 /*
1401 * Need to convert it string by string.
1402 */
1403 else
1404 {
1405 size_t offPathValue = ~(size_t)0;
1406 size_t offDst;
1407
1408 /*
1409 * Estimate the size first.
1410 */
1411 size_t cEnvVars;
1412 size_t cwcDst = 32;
1413 size_t iVar = 0;
1414 const char *pszSrc;
1415 while ((pszSrc = papszEnv[iVar]) != NULL)
1416 {
1417 cwcDst += strlen(pszSrc) + 1;
1418 iVar++;
1419 }
1420 cEnvVars = iVar;
1421
1422 /* Allocate estimated WCHARs and convert the variables one by one, reallocating
1423 the block as needed. */
1424 pwszzDst = (WCHAR *)xmalloc(cwcDst * sizeof(WCHAR));
1425 cwcDst--; /* save one wchar for the terminating empty string. */
1426 offDst = 0;
1427 for (iVar = 0; iVar < cEnvVars; iVar++)
1428 {
1429 size_t cwcLeft = cwcDst - offDst;
1430 size_t const cbSrc = strlen(pszSrc = papszEnv[iVar]) + 1;
1431 assert(cwcDst >= offDst);
1432
1433
1434 cwcRc = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszSrc, cbSrc, &pwszzDst[offDst], cwcLeft);
1435 if (cwcRc > 0)
1436 { /* likely */ }
1437 else
1438 {
1439 dwErr = GetLastError();
1440 if (dwErr == ERROR_INSUFFICIENT_BUFFER)
1441 {
1442 /* Need more space. So, calc exacly how much and resize the block accordingly. */
1443 size_t cbSrc2 = cbSrc;
1444 size_t iVar2 = iVar;
1445 cwcLeft = 1;
1446 for (;;)
1447 {
1448 size_t cwcRc2 = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszSrc, cbSrc, NULL, 0);
1449 if (cwcRc2 > 0)
1450 cwcLeft += cwcRc2;
1451 else
1452 cwcLeft += cbSrc * 4;
1453
1454 /* advance */
1455 iVar2++;
1456 if (iVar2 >= cEnvVars)
1457 break;
1458 pszSrc = papszEnv[iVar2];
1459 cbSrc2 = strlen(pszSrc) + 1;
1460 }
1461 pszSrc = papszEnv[iVar];
1462
1463 /* Grow the allocation and repeat the conversion. */
1464 if (offDst + cwcLeft > cwcDst + 1)
1465 {
1466 cwcDst = offDst + cwcLeft;
1467 pwszzDst = (WCHAR *)xrealloc(pwszzDst, cwcDst * sizeof(WCHAR));
1468 cwcDst--; /* save one wchar for the terminating empty string. */
1469 cwcRc = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszSrc, cbSrc, &pwszzDst[offDst], cwcLeft - 1);
1470 if (cwcRc <= 0)
1471 dwErr = GetLastError();
1472 }
1473 }
1474 if (cwcRc <= 0)
1475 {
1476 fprintf(stderr, _("MultiByteToWideChar failed to convert environment string #%u (%s): %u\n"),
1477 iVar, pszSrc, dwErr);
1478 free(pwszzDst);
1479 return dwErr;
1480 }
1481 }
1482
1483 /* Look for the PATH. */
1484 if ( offPathValue == ~(size_t)0
1485 && IS_PATH_ENV_VAR(cwcRc, &pwszzDst[offDst]) )
1486 offPathValue = offDst + 4 + 1;
1487
1488 /* Advance. */
1489 offDst += cwcRc;
1490 }
1491 pwszzDst[offDst++] = '\0';
1492
1493 if (offPathValue != ~(size_t)0)
1494 *ppwszPath = &pwszzDst[offPathValue];
1495 *ppwszEnv = pwszzDst;
1496 return 0;
1497 }
1498 free(pwszzDst);
1499 return dwErr;
1500}
1501
1502/**
1503 * Childcare worker: handle regular process.
1504 *
1505 * @param pWorker The worker.
1506 * @param pChild The kSubmit child.
1507 */
1508static void mkWinChildcareWorkerThreadHandleProcess(PWINCHILDCAREWORKER pWorker, PWINCHILD pChild)
1509{
1510 WCHAR const *pwszPath = NULL;
1511 WCHAR *pwszzEnvironment = NULL;
1512 WCHAR *pwszCommandLine = NULL;
1513 WCHAR *pwszImageName = NULL;
1514 BOOL fNeedShell = FALSE;
1515 int rc;
1516
1517 /*
1518 * First we convert the environment so we get the PATH we need to
1519 * search for the executable.
1520 */
1521 rc = mkWinChildcareWorkerConvertEnvironment(pChild->u.Process.papszEnv ? pChild->u.Process.papszEnv : environ,
1522 pChild->u.Process.cbEnvStrings,
1523 &pwszzEnvironment, &pwszPath);
1524 /*
1525 * Find the executable and maybe checking if it's a shell script, then
1526 * convert it to a command line.
1527 */
1528 if (rc == 0)
1529 rc = mkWinChildcareWorkerFindImage(pChild->u.Process.papszArgs[0], pwszzEnvironment, pwszPath,
1530 pChild->u.Process.pszShell, &pwszImageName, &fNeedShell);
1531 if (rc == 0)
1532 {
1533 if (!fNeedShell)
1534 rc = mkWinChildcareWorkerConvertCommandline(pChild->u.Process.papszArgs, 0 /*fFlags*/, &pwszCommandLine);
1535 else
1536 rc = mkWinChildcareWorkerConvertCommandlineWithShell(pwszImageName, pChild->u.Process.papszArgs, &pwszCommandLine);
1537
1538 /*
1539 * Create the child process.
1540 */
1541 if (rc == 0)
1542 {
1543 rc = mkWinChildcareWorkerCreateProcess(pWorker, pChild, pwszImageName, pwszCommandLine, pwszzEnvironment);
1544 if (rc == 0)
1545 {
1546 /*
1547 * Wait for the child to complete.
1548 */
1549 mkWinChildcareWorkerWaitForProcess(pWorker, pChild, pChild->u.Process.hProcess);
1550 }
1551 else
1552 pChild->iExitCode = rc;
1553 }
1554 else
1555 pChild->iExitCode = rc;
1556 }
1557 else
1558 pChild->iExitCode = rc;
1559 free(pwszCommandLine);
1560 free(pwszImageName);
1561 free(pwszzEnvironment);
1562}
1563
1564#ifdef KMK
1565
1566/**
1567 * Childcare worker: handle builtin command.
1568 *
1569 * @param pWorker The worker.
1570 * @param pChild The kSubmit child.
1571 */
1572static void mkWinChildcareWorkerThreadHandleBuiltin(PWINCHILDCAREWORKER pWorker, PWINCHILD pChild)
1573{
1574 /** @todo later. */
1575__debugbreak();
1576}
1577
1578/**
1579 * Childcare worker: handle kSubmit job.
1580 *
1581 * @param pWorker The worker.
1582 * @param pChild The kSubmit child.
1583 */
1584static void mkWinChildcareWorkerThreadHandleSubmit(PWINCHILDCAREWORKER pWorker, PWINCHILD pChild)
1585{
1586 void *pvSubmitWorker = pChild->u.Submit.pvSubmitWorker;
1587 for (;;)
1588 {
1589 int iExitCode = -42;
1590 int iSignal = -1;
1591 DWORD dwStatus = WaitForSingleObject(pChild->u.Submit.hEvent, INFINITE);
1592 assert(dwStatus != WAIT_FAILED);
1593
1594 if (kSubmitSubProcGetResult((intptr_t)pvSubmitWorker, &iExitCode, &iSignal) == 0)
1595 {
1596 pChild->iExitCode = iExitCode;
1597 pChild->iSignal = iSignal;
1598 /* Cleanup must be done on the main thread. */
1599 return;
1600 }
1601
1602 if (pChild->iSignal != 0)
1603 kSubmitSubProcKill((intptr_t)pvSubmitWorker, pChild->iSignal);
1604 }
1605}
1606
1607/**
1608 * Childcare worker: handle kmk_redirect process.
1609 *
1610 * @param pWorker The worker.
1611 * @param pChild The redirect child.
1612 */
1613static void mkWinChildcareWorkerThreadHandleRedirect(PWINCHILDCAREWORKER pWorker, PWINCHILD pChild)
1614{
1615 mkWinChildcareWorkerWaitForProcess(pWorker, pChild, pChild->u.Redirect.hProcess);
1616}
1617
1618#endif /* KMK */
1619
1620/**
1621 * Childcare worker thread.
1622 *
1623 * @returns 0
1624 * @param pvUser The worker instance.
1625 */
1626static unsigned int __stdcall mkWinChildcareWorkerThread(void *pvUser)
1627{
1628 PWINCHILDCAREWORKER pWorker = (PWINCHILDCAREWORKER)pvUser;
1629 assert(pWorker->uMagic == WINCHILDCAREWORKER_MAGIC);
1630
1631 /*
1632 * Adjust process group if necessary.
1633 */
1634 if (g_cProcessorGroups > 1)
1635 {
1636 GROUP_AFFINITY Affinity = { ~(ULONG_PTR)0, pWorker->iProcessorGroup, { 0, 0, 0 } };
1637 BOOL fRet = g_pfnSetThreadGroupAffinity(GetCurrentThread(), &Affinity, NULL);
1638 assert(fRet); (void)fRet;
1639 }
1640
1641 /*
1642 * Work loop.
1643 */
1644 while (!g_fShutdown)
1645 {
1646 /*
1647 * Try go idle.
1648 */
1649 PWINCHILD pChild = pWorker->pTailTodoChildren;
1650 if (!pChild)
1651 {
1652 _InterlockedExchange(&pWorker->fIdle, TRUE);
1653 pChild = pWorker->pTailTodoChildren;
1654 if (!pChild)
1655 {
1656 DWORD dwStatus;
1657
1658 _InterlockedIncrement((long *)&g_cIdleChildcareWorkers);
1659 dwStatus = WaitForSingleObject(pWorker->hEvtIdle, INFINITE);
1660 _InterlockedExchange(&pWorker->fIdle, FALSE);
1661 _InterlockedDecrement((long *)&g_cIdleChildcareWorkers);
1662
1663 assert(dwStatus != WAIT_FAILED);
1664 if (dwStatus == WAIT_FAILED)
1665 Sleep(20);
1666
1667 pChild = pWorker->pTailTodoChildren;
1668 }
1669 else
1670 _InterlockedExchange(&pWorker->fIdle, FALSE);
1671 }
1672 if (pChild)
1673 {
1674 /*
1675 * We got work to do. First job is to deque the job.
1676 */
1677 pChild = mkWinChildDequeFromLifo(&pWorker->pTailTodoChildren, pChild);
1678 assert(pChild);
1679 if (pChild)
1680 {
1681 PWINCHILD pTailExpect;
1682
1683 switch (pChild->enmType)
1684 {
1685 case WINCHILDTYPE_PROCESS:
1686 mkWinChildcareWorkerThreadHandleProcess(pWorker, pChild);
1687 break;
1688#ifdef KMK
1689 case WINCHILDTYPE_BUILTIN:
1690 mkWinChildcareWorkerThreadHandleBuiltin(pWorker, pChild);
1691 break;
1692 case WINCHILDTYPE_SUBMIT:
1693 mkWinChildcareWorkerThreadHandleSubmit(pWorker, pChild);
1694 break;
1695 case WINCHILDTYPE_REDIRECT:
1696 mkWinChildcareWorkerThreadHandleRedirect(pWorker, pChild);
1697 break;
1698#endif
1699 default:
1700 assert(0);
1701 }
1702
1703 /*
1704 * Move the child to the completed list.
1705 */
1706 pTailExpect = NULL;
1707 for (;;)
1708 {
1709 PWINCHILD pTailActual;
1710 pChild->pNext = pTailExpect;
1711 pTailActual = _InterlockedCompareExchangePointer(&g_pTailCompletedChildren, pChild, pTailExpect);
1712 if (pTailActual != pTailExpect)
1713 pTailExpect = pTailActual;
1714 else
1715 {
1716 _InterlockedDecrement(&g_cPendingChildren);
1717 if (pTailExpect)
1718 break;
1719 if (SetEvent(g_hEvtWaitChildren))
1720 break;
1721 fprintf(stderr, "SetEvent(g_hEvtWaitChildren=%p) failed: %u\n", g_hEvtWaitChildren, GetLastError());
1722 break;
1723 }
1724 }
1725 }
1726 }
1727 }
1728
1729 _endthreadex(0);
1730 return 0;
1731}
1732
1733/**
1734 * Creates another childcare worker.
1735 *
1736 * @returns The new worker, if we succeeded.
1737 */
1738static PWINCHILDCAREWORKER mkWinChildcareCreateWorker(void)
1739{
1740 PWINCHILDCAREWORKER pWorker = (PWINCHILDCAREWORKER)xcalloc(sizeof(*pWorker));
1741 pWorker->uMagic = WINCHILDCAREWORKER_MAGIC;
1742 pWorker->hEvtIdle = CreateEvent(NULL, FALSE /*fManualReset*/, FALSE /*fInitialState*/, NULL /*pszName*/);
1743 if (pWorker->hEvtIdle)
1744 {
1745 /* Before we start the thread, assign it to a processor group. */
1746 if (g_cProcessorGroups > 1)
1747 {
1748 unsigned int cMaxInGroup;
1749 unsigned int cInGroup;
1750 unsigned int iGroup = g_idxProcessorGroupAllocator % g_cProcessorGroups;
1751 pWorker->iProcessorGroup = iGroup;
1752
1753 /* Advance. We employ a very simple strategy that does 50% in
1754 each group for each group cycle. Odd processor counts are
1755 caught in odd group cycles. The init function selects the
1756 starting group based on make nesting level to avoid stressing
1757 out the first group. */
1758 cInGroup = ++g_idxProcessorInGroupAllocator;
1759 cMaxInGroup = g_pacProcessorsInGroup[iGroup];
1760 if ( !(cMaxInGroup & 1)
1761 || !((g_idxProcessorGroupAllocator / g_cProcessorGroups) & 1))
1762 cMaxInGroup /= 2;
1763 else
1764 cMaxInGroup = cMaxInGroup / 2 + 1;
1765 if (cInGroup >= cMaxInGroup)
1766 {
1767 g_idxProcessorInGroupAllocator = 0;
1768 g_idxProcessorGroupAllocator++;
1769 }
1770 }
1771
1772 /* Try start the thread. */
1773 pWorker->hThread = (HANDLE)_beginthreadex(NULL, 0 /*cbStack*/, mkWinChildcareWorkerThread, pWorker,
1774 0, &pWorker->tid);
1775 if (pWorker->hThread != NULL)
1776 {
1777 g_papChildCareworkers[g_cChildCareworkers++] = pWorker;
1778 return pWorker;
1779 }
1780 CloseHandle(pWorker->hEvtIdle);
1781 }
1782 pWorker->uMagic = ~WINCHILDCAREWORKER_MAGIC;
1783 free(pWorker);
1784 return NULL;
1785}
1786
1787/**
1788 * Helper for copying argument and environment vectors.
1789 *
1790 * @returns Single alloc block copy.
1791 * @param papszSrc The source vector.
1792 * @param pcbStrings Where to return the size of the strings & terminator.
1793 */
1794static char **mkWinChildCopyStringArray(char **papszSrc, size_t *pcbStrings)
1795{
1796 const char *psz;
1797 char **papszDstArray;
1798 char *pszDstStr;
1799 size_t i;
1800
1801 /* Calc sizes first. */
1802 size_t cbStrings = 1; /* (one extra for terminator string) */
1803 size_t cStrings = 0;
1804 while ((psz = papszSrc[cStrings]) != NULL)
1805 {
1806 cbStrings += strlen(psz) + 1;
1807 cStrings++;
1808 }
1809 *pcbStrings = cbStrings;
1810
1811 /* Allocate destination. */
1812 papszDstArray = (char **)xmalloc(cbStrings + (cStrings + 1) * sizeof(papszDstArray[0]));
1813 pszDstStr = (char *)&papszDstArray[cStrings + 1];
1814
1815 /* Copy it. */
1816 for (i = 0; i < cStrings; i++)
1817 {
1818 size_t cbString = strlen(papszSrc[i]) + 1;
1819 papszDstArray[i] = (char *)memcpy(pszDstStr, papszSrc[i], cbString);
1820 pszDstStr += cbString;
1821 }
1822 *pszDstStr = '\0';
1823 assert(&pszDstStr[1] - papszDstArray[0] == cbStrings);
1824 papszDstArray[i] = NULL;
1825 return papszDstArray;
1826}
1827
1828/**
1829 * Allocate and init a WINCHILD.
1830 *
1831 * @returns The new windows child structure.
1832 * @param enmType The child type.
1833 */
1834static PWINCHILD mkWinChildNew(WINCHILDTYPE enmType)
1835{
1836 PWINCHILD pChild = xcalloc(sizeof(*pChild));
1837 pChild->enmType = enmType;
1838 pChild->fCoreDumped = 0;
1839 pChild->iSignal = 0;
1840 pChild->iExitCode = 222222;
1841 pChild->uMagic = WINCHILD_MAGIC;
1842 pChild->pid = (intptr_t)pChild;
1843 return pChild;
1844}
1845
1846/**
1847 * Destructor for WINCHILD.
1848 *
1849 * @param pChild The child structure to destroy.
1850 */
1851static void mkWinChildDelete(PWINCHILD pChild)
1852{
1853 assert(pChild->uMagic == WINCHILD_MAGIC);
1854 pChild->uMagic = ~WINCHILD_MAGIC;
1855
1856 switch (pChild->enmType)
1857 {
1858 case WINCHILDTYPE_PROCESS:
1859 {
1860 if (pChild->u.Process.papszArgs)
1861 {
1862 free(pChild->u.Process.papszArgs);
1863 pChild->u.Process.papszArgs = NULL;
1864 }
1865 if (pChild->u.Process.cbEnvStrings && pChild->u.Process.papszEnv)
1866 {
1867 free(pChild->u.Process.papszEnv);
1868 pChild->u.Process.papszEnv = NULL;
1869 }
1870 if (pChild->u.Process.pszShell)
1871 {
1872 free(pChild->u.Process.pszShell);
1873 pChild->u.Process.pszShell = NULL;
1874 }
1875 if (pChild->u.Process.hProcess)
1876 {
1877 CloseHandle(pChild->u.Process.hProcess);
1878 pChild->u.Process.hProcess = NULL;
1879 }
1880 if ( pChild->u.Process.fCloseStdOut
1881 && pChild->u.Process.hStdOut != INVALID_HANDLE_VALUE)
1882 {
1883 CloseHandle(pChild->u.Process.hStdOut);
1884 pChild->u.Process.hStdOut = INVALID_HANDLE_VALUE;
1885 pChild->u.Process.fCloseStdOut = FALSE;
1886 }
1887 if ( pChild->u.Process.fCloseStdErr
1888 && pChild->u.Process.hStdErr != INVALID_HANDLE_VALUE)
1889 {
1890 CloseHandle(pChild->u.Process.hStdErr);
1891 pChild->u.Process.hStdErr = INVALID_HANDLE_VALUE;
1892 pChild->u.Process.fCloseStdErr = FALSE;
1893 }
1894 break;
1895 }
1896
1897#ifdef KMK
1898
1899 case WINCHILDTYPE_BUILTIN:
1900 assert(0);
1901 break;
1902
1903 case WINCHILDTYPE_SUBMIT:
1904 if (pChild->u.Submit.pvSubmitWorker)
1905 {
1906 kSubmitSubProcCleanup((intptr_t)pChild->u.Submit.pvSubmitWorker);
1907 pChild->u.Submit.pvSubmitWorker = NULL;
1908 }
1909 break;
1910
1911 case WINCHILDTYPE_REDIRECT:
1912 if (pChild->u.Redirect.hProcess)
1913 {
1914 CloseHandle(pChild->u.Redirect.hProcess);
1915 pChild->u.Redirect.hProcess = NULL;
1916 }
1917 break;
1918
1919#endif /* KMK */
1920
1921 default:
1922 assert(0);
1923 }
1924
1925 free(pChild);
1926}
1927
1928/**
1929 * Queues the child with a worker, creating new workers if necessary.
1930 *
1931 * @returns 0 on success, windows error code on failure (child destroyed).
1932 * @param pChild The child.
1933 * @param pPid Where to return the PID (optional).
1934 */
1935static int mkWinChildPushToCareWorker(PWINCHILD pChild, pid_t *pPid)
1936{
1937 PWINCHILDCAREWORKER pWorker = NULL;
1938 PWINCHILD pOldChild;
1939 PWINCHILD pCurChild;
1940
1941 /*
1942 * There are usually idle workers around, except for at the start.
1943 */
1944 if (g_cIdleChildcareWorkers > 0)
1945 {
1946 /*
1947 * Try the idle hint first and move forward from it.
1948 */
1949 unsigned int const cWorkers = g_cChildCareworkers;
1950 unsigned int iHint = g_idxLastChildcareWorker;
1951 unsigned int i;
1952 for (i = iHint; i < cWorkers; i++)
1953 {
1954 PWINCHILDCAREWORKER pPossibleWorker = g_papChildCareworkers[i];
1955 if (pPossibleWorker->fIdle)
1956 {
1957 pWorker = pPossibleWorker;
1958 break;
1959 }
1960 }
1961 if (!pWorker)
1962 {
1963 /* Scan from the start. */
1964 if (iHint > cWorkers)
1965 iHint = cWorkers;
1966 for (i = 0; i < iHint; i++)
1967 {
1968 PWINCHILDCAREWORKER pPossibleWorker = g_papChildCareworkers[i];
1969 if (pPossibleWorker->fIdle)
1970 {
1971 pWorker = pPossibleWorker;
1972 break;
1973 }
1974 }
1975 }
1976 }
1977 if (!pWorker)
1978 {
1979 /*
1980 * Try create more workers if we haven't reached the max yet.
1981 */
1982 if (g_cChildCareworkers < g_cChildCareworkersMax)
1983 pWorker = mkWinChildcareCreateWorker();
1984
1985 /*
1986 * Queue it with an existing worker. Look for one without anthing extra scheduled.
1987 */
1988 if (!pWorker)
1989 {
1990 unsigned int i = g_cChildCareworkers;
1991 if (i == 0)
1992 fatal(NILF, 0, _("Failed to create worker threads for managing child processes!\n"));
1993 pWorker = g_papChildCareworkers[--i];
1994 if (pWorker->pTailTodoChildren)
1995 while (i-- > 0)
1996 {
1997 PWINCHILDCAREWORKER pPossibleWorker = g_papChildCareworkers[i];
1998 if (!pPossibleWorker->pTailTodoChildren)
1999 {
2000 pWorker = pPossibleWorker;
2001 break;
2002 }
2003 }
2004 }
2005 }
2006
2007 /*
2008 * Do the queueing.
2009 */
2010 pOldChild = NULL;
2011 for (;;)
2012 {
2013 pChild->pNext = pOldChild;
2014 pCurChild = _InterlockedCompareExchangePointer((void **)&pWorker->pTailTodoChildren, pChild, pOldChild);
2015 if (pCurChild == pOldChild)
2016 {
2017 DWORD volatile dwErr;
2018 _InterlockedIncrement(&g_cPendingChildren);
2019 if ( !pWorker->fIdle
2020 || SetEvent(pWorker->hEvtIdle))
2021 {
2022 *pPid = pChild->pid;
2023 return 0;
2024 }
2025
2026 _InterlockedDecrement(&g_cPendingChildren);
2027 dwErr = GetLastError();
2028 assert(0);
2029 mkWinChildDelete(pChild);
2030 return dwErr ? dwErr : -1;
2031 }
2032 pOldChild = pCurChild;
2033 }
2034}
2035
2036/**
2037 * Creates a regular child process (job.c).
2038 *
2039 * Will copy the information and push it to a childcare thread that does the
2040 * actual process creation.
2041 *
2042 * @returns 0 on success, windows status code on failure.
2043 * @param papszArgs The arguments.
2044 * @param papszEnv The environment (optional).
2045 * @param pszShell The SHELL variable value (optional).
2046 * @param pMkChild The make child structure (optional).
2047 * @param pPid Where to return the pid.
2048 */
2049int MkWinChildCreate(char **papszArgs, char **papszEnv, const char *pszShell, struct child *pMkChild, pid_t *pPid)
2050{
2051 PWINCHILD pChild = mkWinChildNew(WINCHILDTYPE_PROCESS);
2052 pChild->pMkChild = pMkChild;
2053
2054 pChild->u.Process.papszArgs = mkWinChildCopyStringArray(papszArgs, &pChild->u.Process.cbArgsStrings);
2055 if ( !papszEnv
2056 || !pMkChild
2057 || pMkChild->environment == papszEnv)
2058 {
2059 pChild->u.Process.cbEnvStrings = 0;
2060 pChild->u.Process.papszEnv = papszEnv;
2061 }
2062 else
2063 pChild->u.Process.papszEnv = mkWinChildCopyStringArray(papszEnv, &pChild->u.Process.cbEnvStrings);
2064 if (pszShell)
2065 pChild->u.Process.pszShell = xstrdup(pszShell);
2066 pChild->u.Process.hStdOut = INVALID_HANDLE_VALUE;
2067 pChild->u.Process.hStdErr = INVALID_HANDLE_VALUE;
2068
2069 return mkWinChildPushToCareWorker(pChild, pPid);
2070}
2071
2072/**
2073 * Creates a chile process with a pipe hooked up to stdout.
2074 *
2075 * @returns 0 on success, non-zero on failure.
2076 * @param papszArgs The argument vector.
2077 * @param papszEnv The environment vector (optional).
2078 * @param fdErr File descriptor to hook up to stderr.
2079 * @param pPid Where to return the pid.
2080 * @param pfdReadPipe Where to return the read end of the pipe.
2081 */
2082int MkWinChildCreateWithStdOutPipe(char **papszArgs, char **papszEnv, int fdErr, pid_t *pPid, int *pfdReadPipe)
2083{
2084 /*
2085 * Create the pipe.
2086 */
2087 HANDLE hReadPipe;
2088 HANDLE hWritePipe;
2089 if (CreatePipe(&hReadPipe, &hWritePipe, NULL, 0 /* default size */))
2090 {
2091 if (SetHandleInformation(hWritePipe, HANDLE_FLAG_INHERIT /* clear */ , HANDLE_FLAG_INHERIT /*set*/))
2092 {
2093 int fdReadPipe = _open_osfhandle((intptr_t)hReadPipe, O_RDONLY);
2094 if (fdReadPipe >= 0)
2095 {
2096 PWINCHILD pChild;
2097 int rc;
2098
2099 /*
2100 * Get a handle for fdErr. Ignore failure.
2101 */
2102 HANDLE hStdErr = INVALID_HANDLE_VALUE;
2103 if (fdErr >= 0)
2104 {
2105 HANDLE hNative = (HANDLE)_get_osfhandle(fdErr);
2106 if (!DuplicateHandle(GetCurrentProcess(), hNative, GetCurrentProcess(),
2107 &hStdErr, 0 /*DesiredAccess*/, TRUE /*fInherit*/, DUPLICATE_SAME_ACCESS))
2108 {
2109 ONN(error, NILF, _("DuplicateHandle failed on stderr descriptor (%u): %u\n"), fdErr, GetLastError());
2110 hStdErr = INVALID_HANDLE_VALUE;
2111 }
2112 }
2113
2114 /*
2115 * Push it off to the worker thread.
2116 */
2117 pChild = mkWinChildNew(WINCHILDTYPE_PROCESS);
2118 pChild->u.Process.papszArgs = mkWinChildCopyStringArray(papszArgs, &pChild->u.Process.cbArgsStrings);
2119 pChild->u.Process.papszEnv = mkWinChildCopyStringArray(papszEnv ? papszEnv : environ,
2120 &pChild->u.Process.cbEnvStrings);
2121 //if (pszShell)
2122 // pChild->u.Process.pszShell = xstrdup(pszShell);
2123 pChild->u.Process.hStdOut = hWritePipe;
2124 pChild->u.Process.hStdErr = hStdErr;
2125 pChild->u.Process.fCloseStdErr = TRUE;
2126 pChild->u.Process.fCloseStdOut = TRUE;
2127
2128 rc = mkWinChildPushToCareWorker(pChild, pPid);
2129 if (rc == 0)
2130 *pfdReadPipe = fdReadPipe;
2131 else
2132 {
2133 ON(error, NILF, _("mkWinChildPushToCareWorker failed on pipe: %d\n"), rc);
2134 close(fdReadPipe);
2135 *pfdReadPipe = -1;
2136 *pPid = -1;
2137 }
2138 return rc;
2139 }
2140
2141 ON(error, NILF, _("_open_osfhandle failed on pipe: %u\n"), errno);
2142 }
2143 else
2144 ON(error, NILF, _("SetHandleInformation failed on pipe: %u\n"), GetLastError());
2145 if (hReadPipe != INVALID_HANDLE_VALUE)
2146 CloseHandle(hReadPipe);
2147 CloseHandle(hWritePipe);
2148 }
2149 else
2150 ON(error, NILF, _("CreatePipe failed: %u\n"), GetLastError());
2151 *pfdReadPipe = -1;
2152 *pPid = -1;
2153 return -1;
2154}
2155
2156#ifdef KMK
2157
2158/**
2159 * Interface used by kSubmit.c for registering stuff to wait on.
2160 *
2161 * @returns 0 on success, windows status code on failure.
2162 * @param hEvent The event object handle to wait on.
2163 * @param pvSubmitWorker The argument to pass back to kSubmit to clean up.
2164 * @param pPid Where to return the pid.
2165 */
2166int MkWinChildCreateSubmit(intptr_t hEvent, void *pvSubmitWorker, pid_t *pPid)
2167{
2168 PWINCHILD pChild = mkWinChildNew(WINCHILDTYPE_SUBMIT);
2169 pChild->u.Submit.hEvent = (HANDLE)hEvent;
2170 pChild->u.Submit.pvSubmitWorker = pvSubmitWorker;
2171 return mkWinChildPushToCareWorker(pChild, pPid);
2172}
2173
2174/**
2175 * Interface used by redirect.c for registering stuff to wait on.
2176 *
2177 * @returns 0 on success, windows status code on failure.
2178 * @param hProcess The process object to wait on.
2179 * @param pPid Where to return the pid.
2180 */
2181int MkWinChildCreateRedirect(intptr_t hProcess, pid_t *pPid)
2182{
2183 PWINCHILD pChild = mkWinChildNew(WINCHILDTYPE_REDIRECT);
2184 pChild->u.Redirect.hProcess = (HANDLE)hProcess;
2185 return mkWinChildPushToCareWorker(pChild, pPid);
2186}
2187
2188#endif /* CONFIG_NEW_WIN_CHILDREN */
2189
2190/**
2191 * Interface used to kill process when processing Ctrl-C and fatal errors.
2192 *
2193 * @returns 0 on success, -1+errno on error.
2194 * @param pid The process to kill (PWINCHILD).
2195 * @param iSignal What to kill it with.
2196 * @param pMkChild The make child structure for validation.
2197 */
2198int MkWinChildKill(pid_t pid, int iSignal, struct child *pMkChild)
2199{
2200 PWINCHILD pChild = (PWINCHILD)pid;
2201 if (pChild)
2202 {
2203 assert(pChild->uMagic == WINCHILD_MAGIC);
2204 if (pChild->uMagic == WINCHILD_MAGIC)
2205 {
2206 switch (pChild->enmType)
2207 {
2208 case WINCHILDTYPE_PROCESS:
2209 assert(pChild->pMkChild == pMkChild);
2210 TerminateProcess(pChild->u.Process.hProcess, DBG_TERMINATE_PROCESS);
2211 pChild->iSignal = iSignal;
2212 break;
2213
2214#ifdef KMK
2215
2216 case WINCHILDTYPE_SUBMIT:
2217 {
2218 pChild->iSignal = iSignal;
2219 SetEvent(pChild->u.Submit.hEvent);
2220 break;
2221 }
2222
2223 case WINCHILDTYPE_REDIRECT:
2224 TerminateProcess(pChild->u.Redirect.hProcess, DBG_TERMINATE_PROCESS);
2225 pChild->iSignal = iSignal;
2226 break;
2227
2228 case WINCHILDTYPE_BUILTIN:
2229 break;
2230
2231#endif /* KMK */
2232
2233 default:
2234 assert(0);
2235 }
2236 }
2237 }
2238 return -1;
2239}
2240
2241/**
2242 * Wait for a child process to complete
2243 *
2244 * @returns 0 on success, windows error code on failure.
2245 * @param fBlock Whether to block.
2246 * @param pPid Where to return the pid if a child process
2247 * completed. This is set to zero if none.
2248 * @param piExitCode Where to return the exit code.
2249 * @param piSignal Where to return the exit signal number.
2250 * @param pfCoreDumped Where to return the core dumped indicator.
2251 * @param ppMkChild Where to return the associated struct child pointer.
2252 */
2253int MkWinChildWait(int fBlock, pid_t *pPid, int *piExitCode, int *piSignal, int *pfCoreDumped, struct child **ppMkChild)
2254{
2255 PWINCHILD pChild;
2256
2257 *pPid = 0;
2258 *piExitCode = -222222;
2259 *pfCoreDumped = 0;
2260 *ppMkChild = NULL;
2261
2262 /*
2263 * Wait if necessary.
2264 */
2265 if (fBlock && !g_pTailCompletedChildren && g_cPendingChildren > 0)
2266 {
2267 DWORD dwStatus = WaitForSingleObject(g_hEvtWaitChildren, INFINITE);
2268 if (dwStatus == WAIT_FAILED)
2269 return (int)GetLastError();
2270 }
2271
2272 /*
2273 * Try unlink the last child in the LIFO.
2274 */
2275 pChild = g_pTailCompletedChildren;
2276 if (!pChild)
2277 return 0;
2278 pChild = mkWinChildDequeFromLifo(&g_pTailCompletedChildren, pChild);
2279 assert(pChild);
2280
2281 /*
2282 * Set return values and ditch the child structure.
2283 */
2284 *pPid = pChild->pid;
2285 *piExitCode = pChild->iExitCode;
2286 *pfCoreDumped = pChild->fCoreDumped;
2287 *ppMkChild = pChild->pMkChild;
2288 switch (pChild->enmType)
2289 {
2290 case WINCHILDTYPE_PROCESS:
2291 break;
2292#ifdef KMK
2293 case WINCHILDTYPE_BUILTIN:
2294 break;
2295 case WINCHILDTYPE_SUBMIT:
2296 break;
2297 case WINCHILDTYPE_REDIRECT:
2298 break;
2299#endif /* KMK */
2300 default:
2301 assert(0);
2302 }
2303 mkWinChildDelete(pChild);
2304
2305#ifdef KMK
2306 /* Flush the volatile directory cache. */
2307 dir_cache_invalid_after_job();
2308#endif
2309 return 0;
2310}
2311
2312/**
2313 * Get the child completed event handle.
2314 *
2315 * Needed when w32os.c is waiting for a job token to become available, given
2316 * that completed children is the typical source of these tokens (esp. for kmk).
2317 *
2318 * @returns Event handle.
2319 */
2320intptr_t MkWinChildGetCompleteEventHandle(void)
2321{
2322 return (intptr_t)g_hEvtWaitChildren;
2323}
2324
2325/**
2326 * Emulate execv() for restarting kmk after one ore more makefiles has been
2327 * made.
2328 *
2329 * Does not return.
2330 *
2331 * @param papszArgs The arguments.
2332 * @param papszEnv The environment.
2333 */
2334void MkWinChildReExecMake(char **papszArgs, char **papszEnv)
2335{
2336 PROCESS_INFORMATION ProcInfo;
2337 STARTUPINFOW StartupInfo;
2338 WCHAR *pwszCommandLine;
2339 WCHAR *pwszzEnvironment;
2340 WCHAR *pwszPathIgnored;
2341 int rc;
2342
2343 /*
2344 * Get the executable name.
2345 */
2346 WCHAR wszImageName[MKWINCHILD_MAX_PATH];
2347 DWORD cwcImageName = GetModuleFileNameW(GetModuleHandle(NULL), wszImageName, MKWINCHILD_MAX_PATH);
2348 if (cwcImageName == 0)
2349 ON(fatal, NILF, _("MkWinChildReExecMake: GetModuleFileName failed: %u\n"), GetLastError());
2350
2351 /*
2352 * Create the command line and environment.
2353 */
2354 rc = mkWinChildcareWorkerConvertCommandline(papszArgs, 0 /*fFlags*/, &pwszCommandLine);
2355 if (rc != 0)
2356 ON(fatal, NILF, _("MkWinChildReExecMake: mkWinChildcareWorkerConvertCommandline failed: %u\n"), rc);
2357
2358 rc = mkWinChildcareWorkerConvertEnvironment(papszEnv ? papszEnv : environ, 0 /*cbEnvStrings*/,
2359 &pwszzEnvironment, &pwszPathIgnored);
2360 if (rc != 0)
2361 ON(fatal, NILF, _("MkWinChildReExecMake: mkWinChildcareWorkerConvertEnvironment failed: %u\n"), rc);
2362
2363
2364 /*
2365 * Fill out the startup info and try create the process.
2366 */
2367 memset(&ProcInfo, 0, sizeof(ProcInfo));
2368 memset(&StartupInfo, 0, sizeof(StartupInfo));
2369 StartupInfo.cb = sizeof(StartupInfo);
2370 GetStartupInfoW(&StartupInfo);
2371 if (!CreateProcessW(wszImageName, pwszCommandLine, NULL /*pProcSecAttr*/, NULL /*pThreadSecAttr*/,
2372 TRUE /*fInheritHandles*/, CREATE_UNICODE_ENVIRONMENT, pwszzEnvironment, NULL /*pwsz*/,
2373 &StartupInfo, &ProcInfo))
2374 ON(fatal, NILF, _("MkWinChildReExecMake: CreateProcessW failed: %u\n"), GetLastError());
2375 CloseHandle(ProcInfo.hThread);
2376
2377 /*
2378 * Wait for it to complete and forward the status code to our parent.
2379 */
2380 for (;;)
2381 {
2382 DWORD dwExitCode = -2222;
2383 DWORD dwStatus = WaitForSingleObject(ProcInfo.hProcess, INFINITE);
2384 if ( dwStatus == WAIT_IO_COMPLETION
2385 || dwStatus == WAIT_TIMEOUT /* whatever */)
2386 continue; /* however unlikely, these aren't fatal. */
2387
2388 /* Get the status code and terminate. */
2389 if (dwStatus == WAIT_OBJECT_0)
2390 {
2391 if (!GetExitCodeProcess(ProcInfo.hProcess, &dwExitCode))
2392 {
2393 ON(fatal, NILF, _("MkWinChildReExecMake: GetExitCodeProcess failed: %u\n"), GetLastError());
2394 dwExitCode = -2222;
2395 }
2396 }
2397 else if (dwStatus)
2398 dwExitCode = dwStatus;
2399
2400 CloseHandle(ProcInfo.hProcess);
2401 for (;;)
2402 exit(dwExitCode);
2403 }
2404}
2405
2406/** Serialization with kmkbuiltin_redirect. */
2407void MkWinChildExclusiveAcquire(void)
2408{
2409 AcquireSRWLockExclusive(&g_RWLock);
2410}
2411
2412/** Serialization with kmkbuiltin_redirect. */
2413void MkWinChildExclusiveRelease(void)
2414{
2415 ReleaseSRWLockExclusive(&g_RWLock);
2416}
2417
2418/**
2419 * Implementation of the CLOSE_ON_EXEC macro.
2420 *
2421 * @returns errno value.
2422 * @param fd The file descriptor to hide from children.
2423 */
2424int MkWinChildUnrelatedCloseOnExec(int fd)
2425{
2426 if (fd >= 0)
2427 {
2428 HANDLE hNative = (HANDLE)_get_osfhandle(fd);
2429 if (hNative != INVALID_HANDLE_VALUE && hNative != NULL)
2430 {
2431 if (SetHandleInformation(hNative, HANDLE_FLAG_INHERIT /*clear*/ , 0 /*set*/))
2432 return 0;
2433 }
2434 return errno;
2435 }
2436 return EINVAL;
2437}
2438
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