source: trunk/src/kmk/w32/winchildren.c@ 3179

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

kmk_redirect,winchildren: WOW64 standard handle injection fixes.

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