VirtualBox

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

Last change on this file since 3313 was 3313, checked in by bird, 5 years ago

kmk,kWorker: Assign processor groups to kWorker processes. Added --special-env hack for having a mspdbsrv.exe instance per processor group (using _MSPDBSRV_ENDPOINT_). This was complicated by PCH requiring to share .pdb file and therefore mspdbsrv.exe instance, requiring a mspdb100.dll re-init hack to disconnect kWorker from the previous mspdbsrv when switching. fun.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 128.6 KB
Line 
1/* $Id: winchildren.c 3313 2020-03-16 02:31:38Z 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.
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 * @section sec_win_children_av Remarks on Microsoft Defender and other AV
67 *
68 * Part of the motivation for writing this code was horrible CPU utilization on
69 * a brand new AMD Threadripper 1950X system with lots of memory and SSDs,
70 * running 64-bit Windows 10 build 16299.
71 *
72 * Turns out Microsoft defender adds some overhead to CreateProcess
73 * and other stuff:
74 * - Old make with CreateProcess on main thread:
75 * - With runtime defender enabled: 14 min 6 seconds
76 * - With runtime defender disabled: 4 min 49 seconds
77 * - New make with CreateProcess on worker thread (this code):
78 * - With runtime defender enabled: 6 min 29 seconds
79 * - With runtime defender disabled: 4 min 36 seconds
80 * - With runtime defender disabled out dir only: 5 min 59 seconds
81 *
82 * See also kWorker / kSubmit for more bickering about AV & disk encryption.
83 */
84
85
86/*********************************************************************************************************************************
87* Header Files *
88*********************************************************************************************************************************/
89#include <Windows.h>
90#include <Winternl.h>
91
92#include "../makeint.h"
93#include "../job.h"
94#include "../filedef.h"
95#include "../debug.h"
96#include "../kmkbuiltin.h"
97#include "winchildren.h"
98
99#include <assert.h>
100#include <process.h>
101#include <intrin.h>
102
103#include "nt/nt_child_inject_standard_handles.h"
104#include "console.h"
105
106#ifndef KMK_BUILTIN_STANDALONE
107extern void kmk_cache_exec_image_w(const wchar_t *); /* imagecache.c */
108#endif
109
110
111/*********************************************************************************************************************************
112* Defined Constants And Macros *
113*********************************************************************************************************************************/
114#define MKWINCHILD_MAX_PATH 1024
115
116#define MKWINCHILD_DO_SET_PROCESSOR_GROUP
117
118/** Checks the UTF-16 environment variable pointed to is the PATH. */
119#define IS_PATH_ENV_VAR(a_cwcVar, a_pwszVar) \
120 ( (a_cwcVar) >= 5 \
121 && (a_pwszVar)[4] == L'=' \
122 && ((a_pwszVar)[0] == L'P' || (a_pwszVar)[0] == L'p') \
123 && ((a_pwszVar)[1] == L'A' || (a_pwszVar)[1] == L'a') \
124 && ((a_pwszVar)[2] == L'T' || (a_pwszVar)[2] == L't') \
125 && ((a_pwszVar)[3] == L'H' || (a_pwszVar)[3] == L'h') )
126
127
128/*********************************************************************************************************************************
129* Structures and Typedefs *
130*********************************************************************************************************************************/
131/** Pointer to a childcare worker thread. */
132typedef struct WINCHILDCAREWORKER *PWINCHILDCAREWORKER;
133/** Pointer to a windows child process. */
134typedef struct WINCHILD *PWINCHILD;
135
136
137/**
138 * Child process type.
139 */
140typedef enum WINCHILDTYPE
141{
142 WINCHILDTYPE_INVALID = 0,
143 /** Normal child process. */
144 WINCHILDTYPE_PROCESS,
145#ifdef KMK
146 /** kmkbuiltin command. */
147 WINCHILDTYPE_BUILT_IN,
148 /** kmkbuiltin_append result write out. */
149 WINCHILDTYPE_APPEND,
150 /** kSubmit job. */
151 WINCHILDTYPE_SUBMIT,
152 /** kmk_redirect job. */
153 WINCHILDTYPE_REDIRECT,
154#endif
155 /** End of valid child types. */
156 WINCHILDTYPE_END
157} WINCHILDTYPE;
158
159
160/**
161 * Windows child process.
162 */
163typedef struct WINCHILD
164{
165 /** Magic / eyecatcher (WINCHILD_MAGIC). */
166 ULONG uMagic;
167 /** Child type. */
168 WINCHILDTYPE enmType;
169 /** Pointer to the next child process. */
170 PWINCHILD pNext;
171 /** The pid for this child. */
172 pid_t pid;
173 /** The make child structure associated with this child. */
174 struct child *pMkChild;
175
176 /** The process exit code. */
177 int iExitCode;
178 /** Kill signal, in case we or someone else killed it. */
179 int iSignal;
180 /** Set if core was dumped. */
181 int fCoreDumped;
182 /** Set if the a child process is a candidate for cl.exe where we supress
183 * annoying source name output. */
184 BOOL fProbableClExe;
185 /** The worker executing this child. */
186 PWINCHILDCAREWORKER pWorker;
187
188 /** Type specific data. */
189 union
190 {
191 /** Data for WINCHILDTYPE_PROCESS. */
192 struct
193 {
194 /** Argument vector (single allocation, strings following array). */
195 char **papszArgs;
196 /** Length of the argument strings. */
197 size_t cbArgsStrings;
198 /** Environment vector. Only a copy if fEnvIsCopy is set. */
199 char **papszEnv;
200 /** If we made a copy of the environment, this is the size of the
201 * strings and terminator string (not in array). This is done to
202 * speed up conversion, since MultiByteToWideChar can handle '\0'. */
203 size_t cbEnvStrings;
204 /** The make shell to use (copy). */
205 char *pszShell;
206 /** Handle to use for standard out. */
207 HANDLE hStdOut;
208 /** Handle to use for standard out. */
209 HANDLE hStdErr;
210 /** Whether to close hStdOut after creating the process. */
211 BOOL fCloseStdOut;
212 /** Whether to close hStdErr after creating the process. */
213 BOOL fCloseStdErr;
214 /** Whether to catch output from the process. */
215 BOOL fCatchOutput;
216
217 /** Child process handle. */
218 HANDLE hProcess;
219 } Process;
220
221 /** Data for WINCHILDTYPE_BUILT_IN. */
222 struct
223 {
224 /** The built-in command. */
225 PCKMKBUILTINENTRY pBuiltIn;
226 /** Number of arguments. */
227 int cArgs;
228 /** Argument vector (single allocation, strings following array). */
229 char **papszArgs;
230 /** Environment vector. Only a copy if fEnvIsCopy is set. */
231 char **papszEnv;
232 } BuiltIn;
233
234 /** Data for WINCHILDTYPE_APPEND. */
235 struct
236 {
237 /** The filename. */
238 char *pszFilename;
239 /** How much to append. */
240 size_t cbAppend;
241 /** What to append. */
242 char *pszAppend;
243 /** Whether to truncate the file. */
244 int fTruncate;
245 } Append;
246
247 /** Data for WINCHILDTYPE_SUBMIT. */
248 struct
249 {
250 /** The event we're to wait on (hooked up to a pipe) */
251 HANDLE hEvent;
252 /** Parameter for the cleanup callback. */
253 void *pvSubmitWorker;
254 /** Standard output catching pipe. Optional. */
255 PWINCCWPIPE pStdOut;
256 /** Standard error catching pipe. Optional. */
257 PWINCCWPIPE pStdErr;
258 } Submit;
259
260 /** Data for WINCHILDTYPE_REDIRECT. */
261 struct
262 {
263 /** Child process handle. */
264 HANDLE hProcess;
265 } Redirect;
266 } u;
267
268} WINCHILD;
269/** WINCHILD::uMagic value. */
270#define WINCHILD_MAGIC 0xbabebabeU
271
272
273/**
274 * Data for a windows childcare worker thread.
275 *
276 * We use one worker thread per child, reusing the threads when possible.
277 *
278 * This setup helps avoid the 64-bit handle with the WaitForMultipleObject API.
279 *
280 * It also helps using all CPUs on systems with more than one CPU group
281 * (typically systems with more than 64 CPU threads or/and multiple sockets, or
282 * special configs).
283 *
284 * This helps facilitates using pipes for collecting output child rather
285 * than temporary files. Pipes doesn't involve NTFS and can easily be reused.
286 *
287 * Finally, kBuild specific, this allows running kmkbuiltin_xxxx commands in
288 * threads.
289 */
290typedef struct WINCHILDCAREWORKER
291{
292 /** Magic / eyecatcher (WINCHILDCAREWORKER_MAGIC). */
293 ULONG uMagic;
294 /** The worker index. */
295 unsigned int idxWorker;
296 /** The processor group for this worker. */
297 unsigned int iProcessorGroup;
298 /** The thread ID. */
299 unsigned int tid;
300 /** The thread handle. */
301 HANDLE hThread;
302 /** The event the thread is idling on. */
303 HANDLE hEvtIdle;
304 /** The pipe catching standard output from a child. */
305 PWINCCWPIPE pStdOut;
306 /** The pipe catching standard error from a child. */
307 PWINCCWPIPE pStdErr;
308
309 /** Pointer to the current child. */
310 PWINCHILD volatile pCurChild;
311 /** List of children pending execution on this worker.
312 * This is updated atomitically just like g_pTailCompletedChildren. */
313 PWINCHILD volatile pTailTodoChildren;
314 /** TRUE if idle, FALSE if not. */
315 long volatile fIdle;
316} WINCHILDCAREWORKER;
317/** WINCHILD::uMagic value. */
318#define WINCHILDCAREWORKER_MAGIC 0xdad0dad0U
319
320
321/*********************************************************************************************************************************
322* Global Variables *
323*********************************************************************************************************************************/
324/** Whether it's initialized or not. */
325static BOOL g_fInitialized = FALSE;
326/** Set when we're shutting down everything. */
327static BOOL volatile g_fShutdown = FALSE;
328/** Event used to wait for children. */
329static HANDLE g_hEvtWaitChildren = INVALID_HANDLE_VALUE;
330/** Number of childcare workers currently in g_papChildCareworkers. */
331static unsigned g_cChildCareworkers = 0;
332/** Maximum number of childcare workers in g_papChildCareworkers. */
333static unsigned g_cChildCareworkersMax = 0;
334/** Pointer to childcare workers. */
335static PWINCHILDCAREWORKER *g_papChildCareworkers = NULL;
336/** The processor group allocator state. */
337static MKWINCHILDCPUGROUPALLOCSTATE g_ProcessorGroupAllocator;
338/** Number of processor groups in the system. */
339static unsigned g_cProcessorGroups = 1;
340/** Array detailing how many active processors there are in each group. */
341static unsigned const *g_pacProcessorsInGroup = &g_cProcessorGroups;
342/** Kernel32!GetActiveProcessorGroupCount */
343static WORD (WINAPI *g_pfnGetActiveProcessorGroupCount)(VOID);
344/** Kernel32!GetActiveProcessorCount */
345static DWORD (WINAPI *g_pfnGetActiveProcessorCount)(WORD);
346/** Kernel32!SetThreadGroupAffinity */
347static BOOL (WINAPI *g_pfnSetThreadGroupAffinity)(HANDLE, CONST GROUP_AFFINITY *, GROUP_AFFINITY *);
348/** NTDLL!NtQueryInformationProcess */
349static NTSTATUS (NTAPI *g_pfnNtQueryInformationProcess)(HANDLE, PROCESSINFOCLASS, PVOID, ULONG, PULONG);
350/** Set if the windows host is 64-bit. */
351static BOOL g_f64BitHost = (K_ARCH_BITS == 64);
352/** Windows version info.
353 * @note Putting this before the volatile stuff, hoping to keep it in a
354 * different cache line than the static bits above. */
355static OSVERSIONINFOA g_VersionInfo = { sizeof(g_VersionInfo), 4, 0, 1381, VER_PLATFORM_WIN32_NT, {0} };
356
357/** Children that has been completed.
358 * This is updated atomically, pushing completed children in LIFO fashion
359 * (thus 'tail'), then hitting g_hEvtWaitChildren if head. */
360static PWINCHILD volatile g_pTailCompletedChildren = NULL;
361
362/** Number of idle pending children.
363 * This is updated before g_hEvtWaitChildren is signalled. */
364static unsigned volatile g_cPendingChildren = 0;
365
366/** Number of idle childcare worker threads. */
367static unsigned volatile g_cIdleChildcareWorkers = 0;
368/** Index of the last idle child careworker (just a hint). */
369static unsigned volatile g_idxLastChildcareWorker = 0;
370
371#ifdef WITH_RW_LOCK
372/** RW lock for serializing kmkbuiltin_redirect and CreateProcess. */
373static SRWLOCK g_RWLock;
374#endif
375
376
377#if K_ARCH_BITS == 32 && !defined(_InterlockedCompareExchangePointer)
378/** _InterlockedCompareExchangePointer is missing? (VS2010) */
379K_INLINE void *_InterlockedCompareExchangePointer(void * volatile *ppvDst, void *pvNew, void *pvOld)
380{
381 return (void *)_InterlockedCompareExchange((long volatile *)ppvDst, (intptr_t)pvNew, (intptr_t)pvOld);
382}
383#endif
384
385
386/**
387 * Initializes the windows child module.
388 *
389 * @param cJobSlots The number of job slots.
390 */
391void MkWinChildInit(unsigned int cJobSlots)
392{
393 HMODULE hmod;
394
395 /*
396 * Figure out how many childcare workers first.
397 */
398 static unsigned int const s_cMaxWorkers = 4096;
399 unsigned cWorkers;
400 if (cJobSlots >= 1 && cJobSlots < s_cMaxWorkers)
401 cWorkers = cJobSlots;
402 else
403 cWorkers = s_cMaxWorkers;
404
405 /*
406 * Allocate the array and the child completed event object.
407 */
408 g_papChildCareworkers = (PWINCHILDCAREWORKER *)xcalloc(cWorkers * sizeof(g_papChildCareworkers[0]));
409 g_cChildCareworkersMax = cWorkers;
410
411 g_hEvtWaitChildren = CreateEvent(NULL, FALSE /*fManualReset*/, FALSE /*fInitialState*/, NULL /*pszName*/);
412 if (!g_hEvtWaitChildren)
413 fatal(NILF, INTSTR_LENGTH, _("MkWinChildInit: CreateEvent failed: %u"), GetLastError());
414
415 /*
416 * NTDLL imports that we need.
417 */
418 hmod = GetModuleHandleA("NTDLL.DLL");
419 *(FARPROC *)&g_pfnNtQueryInformationProcess = GetProcAddress(hmod, "NtQueryInformationProcess");
420 if (!g_pfnNtQueryInformationProcess)
421 fatal(NILF, 0, _("MkWinChildInit: NtQueryInformationProcess not found"));
422
423#if K_ARCH_BITS == 32
424 /*
425 * Initialize g_f64BitHost.
426 */
427 if (!IsWow64Process(GetCurrentProcess(), &g_f64BitHost))
428 fatal(NILF, INTSTR_LENGTH, _("MkWinChildInit: IsWow64Process failed: %u"), GetLastError());
429#elif K_ARCH_BITS == 64
430 assert(g_f64BitHost);
431#else
432# error "K_ARCH_BITS is bad/missing"
433#endif
434
435 /*
436 * Figure out how many processor groups there are.
437 * For that we need to first figure the windows version.
438 */
439 if (!GetVersionExA(&g_VersionInfo))
440 {
441 DWORD uRawVer = GetVersion();
442 g_VersionInfo.dwMajorVersion = uRawVer & 0xff;
443 g_VersionInfo.dwMinorVersion = (uRawVer >> 8) & 0xff;
444 g_VersionInfo.dwBuildNumber = (uRawVer >> 16) & 0x7fff;
445 }
446 if (g_VersionInfo.dwMajorVersion >= 6)
447 {
448 hmod = GetModuleHandleA("KERNEL32.DLL");
449 *(FARPROC *)&g_pfnGetActiveProcessorGroupCount = GetProcAddress(hmod, "GetActiveProcessorGroupCount");
450 *(FARPROC *)&g_pfnGetActiveProcessorCount = GetProcAddress(hmod, "GetActiveProcessorCount");
451 *(FARPROC *)&g_pfnSetThreadGroupAffinity = GetProcAddress(hmod, "SetThreadGroupAffinity");
452 if ( g_pfnSetThreadGroupAffinity
453 && g_pfnGetActiveProcessorCount
454 && g_pfnGetActiveProcessorGroupCount)
455 {
456 unsigned int *pacProcessorsInGroup;
457 unsigned iGroup;
458 g_cProcessorGroups = g_pfnGetActiveProcessorGroupCount();
459 if (g_cProcessorGroups == 0)
460 g_cProcessorGroups = 1;
461
462 pacProcessorsInGroup = (unsigned int *)xmalloc(sizeof(g_pacProcessorsInGroup[0]) * g_cProcessorGroups);
463 g_pacProcessorsInGroup = pacProcessorsInGroup;
464 for (iGroup = 0; iGroup < g_cProcessorGroups; iGroup++)
465 pacProcessorsInGroup[iGroup] = g_pfnGetActiveProcessorCount(iGroup);
466
467 MkWinChildInitCpuGroupAllocator(&g_ProcessorGroupAllocator);
468 }
469 else
470 {
471 g_pfnSetThreadGroupAffinity = NULL;
472 g_pfnGetActiveProcessorCount = NULL;
473 g_pfnGetActiveProcessorGroupCount = NULL;
474 }
475 }
476
477#ifdef WITH_RW_LOCK
478 /*
479 * For serializing with standard file handle manipulation (kmkbuiltin_redirect).
480 */
481 InitializeSRWLock(&g_RWLock);
482#endif
483
484 /*
485 * This is dead code that was thought to fix a problem observed doing
486 * `tcc.exe /c "kmk |& tee bld.log"` and leading to a crash in cl.exe
487 * when spawned with fInheritHandles = FALSE, see hStdErr=NULL in the
488 * child. However, it turns out this was probably caused by not clearing
489 * the CRT file descriptor and handle table in the startup info.
490 * Leaving the code here in case it comes in handy after all.
491 */
492#if 0
493 {
494 struct
495 {
496 DWORD uStdHandle;
497 HANDLE hHandle;
498 } aHandles[3] = { { STD_INPUT_HANDLE, NULL }, { STD_OUTPUT_HANDLE, NULL }, { STD_ERROR_HANDLE, NULL } };
499 int i;
500
501 for (i = 0; i < 3; i++)
502 aHandles[i].hHandle = GetStdHandle(aHandles[i].uStdHandle);
503
504 for (i = 0; i < 3; i++)
505 if ( aHandles[i].hHandle == NULL
506 || aHandles[i].hHandle == INVALID_HANDLE_VALUE)
507 {
508 int fd = open("nul", _O_RDWR);
509 if (fd >= 0)
510 {
511 if (_dup2(fd, i) >= 0)
512 {
513 assert((HANDLE)_get_osfhandle(i) != aHandles[i].hHandle);
514 assert((HANDLE)_get_osfhandle(i) == GetStdHandle(aHandles[i].uStdHandle));
515 }
516 else
517 ONNNS(fatal, NILF, "_dup2(%d('nul'), %d) failed: %u (%s)", fd, i, errno, strerror(errno));
518 if (fd != i)
519 close(fd);
520 }
521 else
522 ONNS(fatal, NILF, "open(nul,RW) failed: %u (%s)", i, errno, strerror(errno));
523 }
524 else
525 {
526 int j;
527 for (j = i + 1; j < 3; j++)
528 if (aHandles[j].hHandle == aHandles[i].hHandle)
529 {
530 int fd = _dup(j);
531 if (fd >= 0)
532 {
533 if (_dup2(fd, j) >= 0)
534 {
535 aHandles[j].hHandle = (HANDLE)_get_osfhandle(j);
536 assert(aHandles[j].hHandle != aHandles[i].hHandle);
537 assert(aHandles[j].hHandle == GetStdHandle(aHandles[j].uStdHandle));
538 }
539 else
540 ONNNS(fatal, NILF, "_dup2(%d, %d) failed: %u (%s)", fd, j, errno, strerror(errno));
541 if (fd != j)
542 close(fd);
543 }
544 else
545 ONNS(fatal, NILF, "_dup(%d) failed: %u (%s)", j, errno, strerror(errno));
546 }
547 }
548 }
549#endif
550}
551
552/**
553 * Used by mkWinChildcareWorkerThread() and MkWinChildWait() to get the head
554 * child from a lifo (g_pTailCompletedChildren, pTailTodoChildren).
555 *
556 * @returns Head child.
557 * @param ppTail Pointer to the child variable.
558 * @param pChild Tail child.
559 */
560static PWINCHILD mkWinChildDequeFromLifo(PWINCHILD volatile *ppTail, PWINCHILD pChild)
561{
562 if (pChild->pNext)
563 {
564 PWINCHILD pPrev;
565 do
566 {
567 pPrev = pChild;
568 pChild = pChild->pNext;
569 } while (pChild->pNext);
570 pPrev->pNext = NULL;
571 }
572 else
573 {
574 PWINCHILD const pWantedChild = pChild;
575 pChild = _InterlockedCompareExchangePointer(ppTail, NULL, pWantedChild);
576 if (pChild != pWantedChild)
577 {
578 PWINCHILD pPrev;
579 do
580 {
581 pPrev = pChild;
582 pChild = pChild->pNext;
583 } while (pChild->pNext);
584 pPrev->pNext = NULL;
585 assert(pChild == pWantedChild);
586 }
587 }
588 return pChild;
589}
590
591/**
592 * Output error message while running on a worker thread.
593 *
594 * @returns -1
595 * @param pWorker The calling worker. Mainly for getting the
596 * current child and its stderr output unit. Pass
597 * NULL if the output should go thru the child
598 * stderr buffering.
599 * @param iType The error type:
600 * - 0: more of a info directly to stdout,
601 * - 1: child related error,
602 * - 2: child related error for immedate release.
603 * @param pszFormat The message format string.
604 * @param ... Argument for the message.
605 */
606static int MkWinChildError(PWINCHILDCAREWORKER pWorker, int iType, const char *pszFormat, ...)
607{
608 /*
609 * Format the message into stack buffer.
610 */
611 char szMsg[4096];
612 int cchMsg;
613 int cchPrefix;
614 va_list va;
615
616 /* Compose the prefix, being paranoid about it not exceeding the buffer in any way. */
617 const char *pszInfix = iType == 0 ? "info: " : "error: ";
618 const char *pszProgram = program;
619 if (strlen(pszProgram) > 80)
620 {
621#ifdef KMK
622 pszProgram = "kmk";
623#else
624 pszProgram = "gnumake";
625#endif
626 }
627 if (makelevel == 0)
628 cchPrefix = snprintf(szMsg, sizeof(szMsg) / 2, "%s: %s", pszProgram, pszInfix);
629 else
630 cchPrefix = snprintf(szMsg, sizeof(szMsg) / 2, "%s[%u]: %s", pszProgram, makelevel, pszInfix);
631 assert(cchPrefix < sizeof(szMsg) / 2 && cchPrefix > 0);
632
633 /* Format the user specified message. */
634 va_start(va, pszFormat);
635 cchMsg = vsnprintf(&szMsg[cchPrefix], sizeof(szMsg) - 2 - cchPrefix, pszFormat, va);
636 va_end(va);
637 szMsg[sizeof(szMsg) - 2] = '\0';
638 cchMsg = strlen(szMsg);
639
640 /* Make sure there's a newline at the end of it (we reserved space for that). */
641 if (cchMsg <= 0 || szMsg[cchMsg - 1] != '\n')
642 {
643 szMsg[cchMsg++] = '\n';
644 szMsg[cchMsg] = '\0';
645 }
646
647#ifdef CONFIG_WITH_OUTPUT_IN_MEMORY
648 /*
649 * Try use the stderr of the current child of the worker.
650 */
651 if ( iType != 0
652 && iType != 3
653 && pWorker)
654 {
655 PWINCHILD pChild = pWorker->pCurChild;
656 if (pChild)
657 {
658 struct child *pMkChild = pChild->pMkChild;
659 if (pMkChild)
660 {
661 output_write_text(&pMkChild->output, 1 /*is_err*/, szMsg, cchMsg);
662 return -1;
663 }
664 }
665 }
666#endif
667
668 /*
669 * Fallback to writing directly to stderr.
670 */
671 maybe_con_fwrite(szMsg, cchMsg, 1, iType == 0 ? stdout : stderr);
672 return -1;
673}
674
675/**
676 * Duplicates the given UTF-16 string.
677 *
678 * @returns 0
679 * @param pwszSrc The UTF-16 string to duplicate.
680 * @param cwcSrc Length, may include the terminator.
681 * @param ppwszDst Where to return the duplicate.
682 */
683static int mkWinChildDuplicateUtf16String(const WCHAR *pwszSrc, size_t cwcSrc, WCHAR **ppwszDst)
684{
685 size_t cb = sizeof(WCHAR) * cwcSrc;
686 if (cwcSrc > 0 && pwszSrc[cwcSrc - 1] == L'\0')
687 *ppwszDst = (WCHAR *)memcpy(xmalloc(cb), pwszSrc, cb);
688 else
689 {
690 WCHAR *pwszDst = (WCHAR *)xmalloc(cb + sizeof(WCHAR));
691 memcpy(pwszDst, pwszSrc, cb);
692 pwszDst[cwcSrc] = L'\0';
693 *ppwszDst = pwszDst;
694 }
695 return 0;
696}
697
698
699/**
700 * Used to flush data we're read but not yet written at the termination of a
701 * process.
702 *
703 * @param pChild The child.
704 * @param pPipe The pipe.
705 */
706static void mkWinChildcareWorkerFlushUnwritten(PWINCHILD pChild, PWINCCWPIPE pPipe)
707{
708 DWORD cbUnwritten = pPipe->offPendingRead - pPipe->cbWritten;
709 assert(pPipe->cbWritten <= pPipe->cbBuffer - 16);
710 assert(pPipe->offPendingRead <= pPipe->cbBuffer - 16);
711 if (cbUnwritten)
712 {
713#ifdef CONFIG_WITH_OUTPUT_IN_MEMORY
714 if (pChild && pChild->pMkChild)
715 {
716 output_write_bin(&pChild->pMkChild->output, pPipe->iWhich == 2, &pPipe->pbBuffer[pPipe->cbWritten], cbUnwritten);
717 pPipe->cbWritten += cbUnwritten;
718 }
719 else
720#endif
721 {
722 DWORD cbWritten = 0;
723 if (WriteFile(GetStdHandle(pPipe->iWhich == 1 ? STD_OUTPUT_HANDLE : STD_ERROR_HANDLE),
724 &pPipe->pbBuffer[pPipe->cbWritten], cbUnwritten, &cbWritten, NULL))
725 pPipe->cbWritten += cbWritten <= cbUnwritten ? cbWritten : cbUnwritten; /* paranoia */
726 }
727 pPipe->fHaveWrittenOut = TRUE;
728 }
729}
730
731/**
732 * This logic mirrors kwSandboxConsoleFlushAll.
733 *
734 * @returns TRUE if it looks like a CL.EXE source line, otherwise FALSE.
735 * @param pPipe The pipe.
736 * @param offStart The start of the output in the pipe buffer.
737 * @param offEnd The end of the output in the pipe buffer.
738 */
739static BOOL mkWinChildcareWorkerIsClExeSourceLine(PWINCCWPIPE pPipe, DWORD offStart, DWORD offEnd)
740{
741 if (offEnd < offStart + 2)
742 return FALSE;
743 if (offEnd - offStart > 80)
744 return FALSE;
745
746 if ( pPipe->pbBuffer[offEnd - 2] != '\r'
747 || pPipe->pbBuffer[offEnd - 1] != '\n')
748 return FALSE;
749
750 offEnd -= 2;
751 while (offEnd-- > offStart)
752 {
753 char ch = pPipe->pbBuffer[offEnd];
754 if (isalnum(ch) || ch == '.' || ch == ' ' || ch == '_' || ch == '-')
755 { /* likely */ }
756 else
757 return FALSE;
758 }
759
760 return TRUE;
761}
762
763/**
764 * Adds output to the given standard output for the child.
765 *
766 * There is no pending read when this function is called, so we're free to
767 * reshuffle the buffer if desirable.
768 *
769 * @param pChild The child. Optional (kSubmit).
770 * @param iWhich Which standard descriptor number.
771 * @param cbNewData How much more output was caught.
772 */
773static void mkWinChildcareWorkerCaughtMoreOutput(PWINCHILD pChild, PWINCCWPIPE pPipe, DWORD cbNewData)
774{
775 DWORD offStart = pPipe->cbWritten;
776 assert(offStart <= pPipe->offPendingRead);
777 assert(offStart <= pPipe->cbBuffer - 16);
778 assert(pPipe->offPendingRead <= pPipe->cbBuffer - 16);
779 if (cbNewData > 0)
780 {
781 DWORD offRest;
782
783 /* Move offPendingRead ahead by cbRead. */
784 pPipe->offPendingRead += cbNewData;
785 assert(pPipe->offPendingRead <= pPipe->cbBuffer);
786 if (pPipe->offPendingRead > pPipe->cbBuffer)
787 pPipe->offPendingRead = pPipe->cbBuffer;
788
789 /* Locate the last newline in the buffer. */
790 offRest = pPipe->offPendingRead;
791 while (offRest > offStart && pPipe->pbBuffer[offRest - 1] != '\n')
792 offRest--;
793
794 /* If none were found and we've less than 16 bytes left in the buffer, try
795 find a word boundrary to flush on instead. */
796 if ( offRest <= offStart
797 && pPipe->cbBuffer - pPipe->offPendingRead + offStart < 16)
798 {
799 offRest = pPipe->offPendingRead;
800 while ( offRest > offStart
801 && isalnum(pPipe->pbBuffer[offRest - 1]))
802 offRest--;
803 if (offRest == offStart)
804 offRest = pPipe->offPendingRead;
805 }
806 /* If this is a potential CL.EXE process, we will keep the source
807 filename unflushed and maybe discard it at the end. */
808 else if ( pChild
809 && pChild->fProbableClExe
810 && pPipe->iWhich == 1
811 && offRest == pPipe->offPendingRead
812 && mkWinChildcareWorkerIsClExeSourceLine(pPipe, offStart, offRest))
813 offRest = offStart;
814
815 if (offRest > offStart)
816 {
817 /* Write out offStart..offRest. */
818 DWORD cbToWrite = offRest - offStart;
819#ifdef CONFIG_WITH_OUTPUT_IN_MEMORY
820 if (pChild && pChild->pMkChild)
821 {
822 output_write_bin(&pChild->pMkChild->output, pPipe->iWhich == 2, &pPipe->pbBuffer[offStart], cbToWrite);
823 offStart += cbToWrite;
824 pPipe->cbWritten = offStart;
825 }
826 else
827#endif
828 {
829 DWORD cbWritten = 0;
830 if (WriteFile(GetStdHandle(pPipe->iWhich == 1 ? STD_OUTPUT_HANDLE : STD_ERROR_HANDLE),
831 &pPipe->pbBuffer[offStart], cbToWrite, &cbWritten, NULL))
832 {
833 offStart += cbWritten <= cbToWrite ? cbWritten : cbToWrite; /* paranoia */
834 pPipe->cbWritten = offStart;
835 }
836 }
837 pPipe->fHaveWrittenOut = TRUE;
838 }
839 }
840
841 /* Shuffle the data to the front of the buffer. */
842 if (offStart > 0)
843 {
844 DWORD cbUnwritten = pPipe->offPendingRead - offStart;
845 if (cbUnwritten > 0)
846 memmove(pPipe->pbBuffer, &pPipe->pbBuffer[offStart], cbUnwritten);
847 pPipe->offPendingRead -= pPipe->cbWritten;
848 pPipe->cbWritten = 0;
849 }
850}
851
852/**
853 * Catches output from the given pipe.
854 *
855 * @param pChild The child. Optional (kSubmit).
856 * @param pPipe The pipe.
857 * @param fDraining Set if we're draining the pipe after the process
858 * terminated.
859 */
860static void mkWinChildcareWorkerCatchOutput(PWINCHILD pChild, PWINCCWPIPE pPipe, BOOL fDraining)
861{
862 /*
863 * Deal with already pending read.
864 */
865 if (pPipe->fReadPending)
866 {
867 DWORD cbRead = 0;
868 if (GetOverlappedResult(pPipe->hPipeMine, &pPipe->Overlapped, &cbRead, !fDraining))
869 {
870 mkWinChildcareWorkerCaughtMoreOutput(pChild, pPipe, cbRead);
871 pPipe->fReadPending = FALSE;
872 }
873 else if (fDraining && GetLastError() == ERROR_IO_INCOMPLETE)
874 return;
875 else
876 {
877 MkWinChildError(pChild ? pChild->pWorker : NULL, 2, "GetOverlappedResult failed: %u\n", GetLastError());
878 pPipe->fReadPending = FALSE;
879 if (fDraining)
880 return;
881 }
882 }
883
884 /*
885 * Read data till one becomes pending.
886 */
887 for (;;)
888 {
889 DWORD cbRead;
890
891 memset(&pPipe->Overlapped, 0, sizeof(pPipe->Overlapped));
892 pPipe->Overlapped.hEvent = pPipe->hEvent;
893 ResetEvent(pPipe->hEvent);
894
895 assert(pPipe->offPendingRead < pPipe->cbBuffer);
896 SetLastError(0);
897 cbRead = 0;
898 if (!ReadFile(pPipe->hPipeMine, &pPipe->pbBuffer[pPipe->offPendingRead],
899 pPipe->cbBuffer - pPipe->offPendingRead, &cbRead, &pPipe->Overlapped))
900 {
901 DWORD dwErr = GetLastError();
902 if (dwErr == ERROR_IO_PENDING)
903 pPipe->fReadPending = TRUE;
904 else
905 MkWinChildError(pChild ? pChild->pWorker : NULL, 2,
906 "ReadFile failed on standard %s: %u\n",
907 pPipe->iWhich == 1 ? "output" : "error", GetLastError());
908 return;
909 }
910
911 mkWinChildcareWorkerCaughtMoreOutput(pChild, pPipe, cbRead);
912 }
913}
914
915/**
916 * Makes sure the output pipes are drained and pushed to output.
917 *
918 * @param pChild The child. Optional (kSubmit).
919 * @param pStdOut The standard output pipe structure.
920 * @param pStdErr The standard error pipe structure.
921 */
922void MkWinChildcareWorkerDrainPipes(PWINCHILD pChild, PWINCCWPIPE pStdOut, PWINCCWPIPE pStdErr)
923{
924 mkWinChildcareWorkerCatchOutput(pChild, pStdOut, TRUE /*fDraining*/);
925 mkWinChildcareWorkerCatchOutput(pChild, pStdErr, TRUE /*fDraining*/);
926
927 /* Drop lone 'source.c' line from CL.exe, but only if no other output at all. */
928 if ( pChild
929 && pChild->fProbableClExe
930 && !pStdOut->fHaveWrittenOut
931 && !pStdErr->fHaveWrittenOut
932 && pStdErr->cbWritten == pStdErr->offPendingRead
933 && pStdOut->cbWritten < pStdOut->offPendingRead
934 && mkWinChildcareWorkerIsClExeSourceLine(pStdOut, pStdOut->cbWritten, pStdOut->offPendingRead))
935 {
936 if (!pStdOut->fReadPending)
937 pStdOut->cbWritten = pStdOut->offPendingRead = 0;
938 else
939 pStdOut->cbWritten = pStdOut->offPendingRead;
940 }
941 else
942 {
943 mkWinChildcareWorkerFlushUnwritten(pChild, pStdOut);
944 mkWinChildcareWorkerFlushUnwritten(pChild, pStdErr);
945 }
946}
947
948/**
949 * Commmon worker for waiting on a child process and retrieving the exit code.
950 *
951 * @returns Child exit code.
952 * @param pWorker The worker.
953 * @param pChild The child.
954 * @param hProcess The process handle.
955 * @param pwszJob The job name.
956 * @param fCatchOutput Set if we need to work the output pipes
957 * associated with the worker.
958 */
959static int mkWinChildcareWorkerWaitForProcess(PWINCHILDCAREWORKER pWorker, PWINCHILD pChild, HANDLE hProcess,
960 WCHAR const *pwszJob, BOOL fCatchOutput)
961{
962 DWORD const msStart = GetTickCount();
963 DWORD msNextMsg = msStart + 15000;
964
965 /* Reset the written indicators on the pipes before we start loop. */
966 pWorker->pStdOut->fHaveWrittenOut = FALSE;
967 pWorker->pStdErr->fHaveWrittenOut = FALSE;
968
969 for (;;)
970 {
971 /*
972 * Do the waiting and output catching.
973 */
974 DWORD dwStatus;
975 if (!fCatchOutput)
976 dwStatus = WaitForSingleObject(hProcess, 15001 /*ms*/);
977 else
978 {
979 HANDLE ahHandles[3] = { hProcess, pWorker->pStdOut->hEvent, pWorker->pStdErr->hEvent };
980 dwStatus = WaitForMultipleObjects(3, ahHandles, FALSE /*fWaitAll*/, 1000 /*ms*/);
981 if (dwStatus == WAIT_OBJECT_0 + 1)
982 mkWinChildcareWorkerCatchOutput(pChild, pWorker->pStdOut, FALSE /*fDraining*/);
983 else if (dwStatus == WAIT_OBJECT_0 + 2)
984 mkWinChildcareWorkerCatchOutput(pChild, pWorker->pStdErr, FALSE /*fDraining*/);
985 }
986 assert(dwStatus != WAIT_FAILED);
987
988 /*
989 * Get the exit code and return if the process was signalled as done.
990 */
991 if (dwStatus == WAIT_OBJECT_0)
992 {
993 DWORD dwExitCode = -42;
994 if (GetExitCodeProcess(hProcess, &dwExitCode))
995 {
996 pChild->iExitCode = (int)dwExitCode;
997 if (fCatchOutput)
998 MkWinChildcareWorkerDrainPipes(pChild, pWorker->pStdOut, pWorker->pStdErr);
999 return dwExitCode;
1000 }
1001 }
1002 /*
1003 * Loop again if just a timeout or pending output?
1004 * Put out a message every 15 or 30 seconds if the job takes a while.
1005 */
1006 else if ( dwStatus == WAIT_TIMEOUT
1007 || dwStatus == WAIT_OBJECT_0 + 1
1008 || dwStatus == WAIT_OBJECT_0 + 2
1009 || dwStatus == WAIT_IO_COMPLETION)
1010 {
1011 DWORD msNow = GetTickCount();
1012 if (msNow >= msNextMsg)
1013 {
1014 if ( !pChild->pMkChild
1015 || !pChild->pMkChild->recursive) /* ignore make recursions */
1016 {
1017 if ( !pChild->pMkChild
1018 || !pChild->pMkChild->file
1019 || !pChild->pMkChild->file->name)
1020 MkWinChildError(NULL, 0, "Pid %u ('%ls') still running after %u seconds\n",
1021 GetProcessId(hProcess), pwszJob, (msNow - msStart) / 1000);
1022 else
1023 MkWinChildError(NULL, 0, "Target '%s' (pid %u) still running after %u seconds\n",
1024 pChild->pMkChild->file->name, GetProcessId(hProcess), (msNow - msStart) / 1000);
1025 }
1026
1027 /* After 15s, 30s, 60s, 120s, 180s, ... */
1028 if (msNextMsg == msStart + 15000)
1029 msNextMsg += 15000;
1030 else
1031 msNextMsg += 30000;
1032 }
1033 continue;
1034 }
1035
1036 /* Something failed. */
1037 pChild->iExitCode = GetLastError();
1038 if (pChild->iExitCode == 0)
1039 pChild->iExitCode = -4242;
1040 return pChild->iExitCode;
1041 }
1042}
1043
1044
1045/**
1046 * Closes standard handles that need closing before destruction.
1047 *
1048 * @param pChild The child (WINCHILDTYPE_PROCESS).
1049 */
1050static void mkWinChildcareWorkerCloseStandardHandles(PWINCHILD pChild)
1051{
1052 if ( pChild->u.Process.fCloseStdOut
1053 && pChild->u.Process.hStdOut != INVALID_HANDLE_VALUE)
1054 {
1055 CloseHandle(pChild->u.Process.hStdOut);
1056 pChild->u.Process.hStdOut = INVALID_HANDLE_VALUE;
1057 pChild->u.Process.fCloseStdOut = FALSE;
1058 }
1059 if ( pChild->u.Process.fCloseStdErr
1060 && pChild->u.Process.hStdErr != INVALID_HANDLE_VALUE)
1061 {
1062 CloseHandle(pChild->u.Process.hStdErr);
1063 pChild->u.Process.hStdErr = INVALID_HANDLE_VALUE;
1064 pChild->u.Process.fCloseStdErr = FALSE;
1065 }
1066}
1067
1068
1069/**
1070 * Does the actual process creation given.
1071 *
1072 * @returns 0 if there is anything to wait on, otherwise non-zero windows error.
1073 * @param pWorker The childcare worker.
1074 * @param pChild The child.
1075 * @param pwszImageName The image path.
1076 * @param pwszCommandLine The command line.
1077 * @param pwszzEnvironment The enviornment block.
1078 */
1079static int mkWinChildcareWorkerCreateProcess(PWINCHILDCAREWORKER pWorker, WCHAR const *pwszImageName,
1080 WCHAR const *pwszCommandLine, WCHAR const *pwszzEnvironment, WCHAR const *pwszCwd,
1081 BOOL pafReplace[3], HANDLE pahChild[3], BOOL fCatchOutput, HANDLE *phProcess)
1082{
1083 PROCESS_INFORMATION ProcInfo;
1084 STARTUPINFOW StartupInfo;
1085 DWORD fFlags = CREATE_UNICODE_ENVIRONMENT;
1086 BOOL const fHaveHandles = pafReplace[0] | pafReplace[1] | pafReplace[2];
1087 BOOL fRet;
1088 DWORD dwErr;
1089#ifdef KMK
1090 extern int process_priority;
1091#endif
1092
1093 /*
1094 * Populate startup info.
1095 *
1096 * Turns out we can get away without passing TRUE for the inherit handles
1097 * parameter to CreateProcess when we're not using STARTF_USESTDHANDLES.
1098 * At least on NT, which is all worth caring about at this point + context IMO.
1099 *
1100 * Not inherting the handles is a good thing because it means we won't
1101 * accidentally end up with a pipe handle or such intended for a different
1102 * child process, potentially causing the EOF/HUP event to be delayed.
1103 *
1104 * Since the present handle inhertiance requirements only involves standard
1105 * output and error, we'll never set the inherit handles flag and instead
1106 * do manual handle duplication and planting.
1107 */
1108 memset(&StartupInfo, 0, sizeof(StartupInfo));
1109 StartupInfo.cb = sizeof(StartupInfo);
1110 GetStartupInfoW(&StartupInfo);
1111 StartupInfo.lpReserved2 = 0; /* No CRT file handle + descriptor info possible, sorry. */
1112 StartupInfo.cbReserved2 = 0;
1113 if ( !fHaveHandles
1114 && !fCatchOutput)
1115 StartupInfo.dwFlags &= ~STARTF_USESTDHANDLES;
1116 else
1117 {
1118 fFlags |= CREATE_SUSPENDED;
1119 StartupInfo.dwFlags &= ~STARTF_USESTDHANDLES;
1120 }
1121
1122 /*
1123 * Flags.
1124 */
1125#ifdef KMK
1126 switch (process_priority)
1127 {
1128 case 1: fFlags |= CREATE_SUSPENDED | IDLE_PRIORITY_CLASS; break;
1129 case 2: fFlags |= CREATE_SUSPENDED | BELOW_NORMAL_PRIORITY_CLASS; break;
1130 case 3: fFlags |= CREATE_SUSPENDED | NORMAL_PRIORITY_CLASS; break;
1131 case 4: fFlags |= CREATE_SUSPENDED | HIGH_PRIORITY_CLASS; break;
1132 case 5: fFlags |= CREATE_SUSPENDED | REALTIME_PRIORITY_CLASS; break;
1133 }
1134#endif
1135 if (g_cProcessorGroups > 1)
1136 fFlags |= CREATE_SUSPENDED;
1137
1138 /*
1139 * Try create the process.
1140 */
1141 DB(DB_JOBS, ("CreateProcessW(%ls, %ls,,, TRUE, %#x...)\n", pwszImageName, pwszCommandLine, fFlags));
1142 memset(&ProcInfo, 0, sizeof(ProcInfo));
1143#ifdef WITH_RW_LOCK
1144 AcquireSRWLockShared(&g_RWLock);
1145#endif
1146
1147 fRet = CreateProcessW((WCHAR *)pwszImageName, (WCHAR *)pwszCommandLine, NULL /*pProcSecAttr*/, NULL /*pThreadSecAttr*/,
1148 FALSE /*fInheritHandles*/, fFlags, (WCHAR *)pwszzEnvironment, pwszCwd, &StartupInfo, &ProcInfo);
1149 dwErr = GetLastError();
1150
1151#ifdef WITH_RW_LOCK
1152 ReleaseSRWLockShared(&g_RWLock);
1153#endif
1154 if (fRet)
1155 *phProcess = ProcInfo.hProcess;
1156 else
1157 {
1158 MkWinChildError(pWorker, 1, "CreateProcess(%ls) failed: %u\n", pwszImageName, dwErr);
1159 return (int)dwErr;
1160 }
1161
1162 /*
1163 * If the child is suspended, we've got some adjustment work to be done.
1164 */
1165 dwErr = ERROR_SUCCESS;
1166 if (fFlags & CREATE_SUSPENDED)
1167 {
1168 /*
1169 * First do handle inhertiance as that's the most complicated.
1170 */
1171 if (fHaveHandles || fCatchOutput)
1172 {
1173 char szErrMsg[128];
1174 if (fCatchOutput)
1175 {
1176 if (!pafReplace[1])
1177 {
1178 pafReplace[1] = TRUE;
1179 pahChild[1] = pWorker->pStdOut->hPipeChild;
1180 }
1181 if (!pafReplace[2])
1182 {
1183 pafReplace[2] = TRUE;
1184 pahChild[2] = pWorker->pStdErr->hPipeChild;
1185 }
1186 }
1187 dwErr = nt_child_inject_standard_handles(ProcInfo.hProcess, pafReplace, pahChild, szErrMsg, sizeof(szErrMsg));
1188 if (dwErr != 0)
1189 MkWinChildError(pWorker, 1, "%s\n", szErrMsg);
1190 }
1191
1192 /*
1193 * Assign processor group (ignore failure).
1194 */
1195#ifdef MKWINCHILD_DO_SET_PROCESSOR_GROUP
1196 if (g_cProcessorGroups > 1)
1197 {
1198 GROUP_AFFINITY Affinity = { 0, pWorker->iProcessorGroup, { 0, 0, 0 } };
1199 fRet = g_pfnSetThreadGroupAffinity(ProcInfo.hThread, &Affinity, NULL);
1200 assert(fRet);
1201 }
1202#endif
1203
1204#ifdef KMK
1205 /*
1206 * Set priority (ignore failure).
1207 */
1208 switch (process_priority)
1209 {
1210 case 1: fRet = SetThreadPriority(ProcInfo.hThread, THREAD_PRIORITY_IDLE); break;
1211 case 2: fRet = SetThreadPriority(ProcInfo.hThread, THREAD_PRIORITY_BELOW_NORMAL); break;
1212 case 3: fRet = SetThreadPriority(ProcInfo.hThread, THREAD_PRIORITY_NORMAL); break;
1213 case 4: fRet = SetThreadPriority(ProcInfo.hThread, THREAD_PRIORITY_HIGHEST); break;
1214 case 5: fRet = SetThreadPriority(ProcInfo.hThread, THREAD_PRIORITY_TIME_CRITICAL); break;
1215 default: fRet = TRUE;
1216 }
1217 assert(fRet);
1218#endif
1219
1220 /*
1221 * Resume the thread if the adjustments succeeded, otherwise kill it.
1222 */
1223 if (dwErr == ERROR_SUCCESS)
1224 {
1225 fRet = ResumeThread(ProcInfo.hThread);
1226 assert(fRet);
1227 if (!fRet)
1228 {
1229 dwErr = GetLastError();
1230 MkWinChildError(pWorker, 1, "ResumeThread failed on child process: %u\n", dwErr);
1231 }
1232 }
1233 if (dwErr != ERROR_SUCCESS)
1234 TerminateProcess(ProcInfo.hProcess, dwErr);
1235 }
1236
1237 /*
1238 * Close unnecessary handles and cache the image.
1239 */
1240 CloseHandle(ProcInfo.hThread);
1241 kmk_cache_exec_image_w(pwszImageName);
1242 return 0;
1243}
1244
1245/**
1246 * Converts a argument vector that has already been quoted correctly.
1247 *
1248 * The argument vector is typically the result of quote_argv().
1249 *
1250 * @returns 0 on success, non-zero on failure.
1251 * @param pWorker The childcare worker.
1252 * @param papszArgs The argument vector to convert.
1253 * @param ppwszCommandLine Where to return the command line.
1254 */
1255static int mkWinChildcareWorkerConvertQuotedArgvToCommandline(PWINCHILDCAREWORKER pWorker, char **papszArgs,
1256 WCHAR **ppwszCommandLine)
1257{
1258 WCHAR *pwszCmdLine;
1259 WCHAR *pwszDst;
1260
1261 /*
1262 * Calc length the converted length.
1263 */
1264 unsigned cwcNeeded = 1;
1265 unsigned i = 0;
1266 const char *pszSrc;
1267 while ((pszSrc = papszArgs[i]) != NULL)
1268 {
1269 int cwcThis = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszSrc, -1, NULL, 0);
1270 if (cwcThis > 0 || *pszSrc == '\0')
1271 cwcNeeded += cwcThis + 1;
1272 else
1273 {
1274 DWORD dwErr = GetLastError();
1275 MkWinChildError(pWorker, 1, _("MultiByteToWideChar failed to convert argv[%u] (%s): %u\n"), i, pszSrc, dwErr);
1276 return dwErr;
1277 }
1278 i++;
1279 }
1280
1281 /*
1282 * Allocate and do the conversion.
1283 */
1284 pwszCmdLine = pwszDst = (WCHAR *)xmalloc(cwcNeeded * sizeof(WCHAR));
1285 i = 0;
1286 while ((pszSrc = papszArgs[i]) != NULL)
1287 {
1288 int cwcThis;
1289 if (i > 0)
1290 {
1291 *pwszDst++ = ' ';
1292 cwcNeeded--;
1293 }
1294
1295 cwcThis = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszSrc, -1, pwszDst, cwcNeeded);
1296 if (!cwcThis && *pszSrc != '\0')
1297 {
1298 DWORD dwErr = GetLastError();
1299 MkWinChildError(pWorker, 1, _("MultiByteToWideChar failed to convert argv[%u] (%s): %u\n"), i, pszSrc, dwErr);
1300 free(pwszCmdLine);
1301 return dwErr;
1302 }
1303 if (cwcThis > 0 && pwszDst[cwcThis - 1] == '\0')
1304 cwcThis--;
1305 pwszDst += cwcThis;
1306 cwcNeeded -= cwcThis;
1307 i++;
1308 }
1309 *pwszDst++ = '\0';
1310
1311 *ppwszCommandLine = pwszCmdLine;
1312 return 0;
1313}
1314
1315
1316#define MKWCCWCMD_F_CYGWIN_SHELL 1
1317#define MKWCCWCMD_F_MKS_SHELL 2
1318#define MKWCCWCMD_F_HAVE_SH 4
1319#define MKWCCWCMD_F_HAVE_KASH_C 8 /**< kmk_ash -c "..." */
1320
1321/*
1322 * @param pWorker The childcare worker if on one, otherwise NULL.
1323 */
1324static int mkWinChildcareWorkerConvertCommandline(PWINCHILDCAREWORKER pWorker, char **papszArgs, unsigned fFlags,
1325 WCHAR **ppwszCommandLine)
1326{
1327 struct ARGINFO
1328 {
1329 size_t cchSrc;
1330 size_t cwcDst; /**< converted size w/o terminator. */
1331 size_t cwcDstExtra : 24; /**< Only set with fSlowly. */
1332 size_t fSlowly : 1;
1333 size_t fQuoteIt : 1;
1334 size_t fEndSlashes : 1; /**< if escapes needed for trailing backslashes. */
1335 size_t fExtraSpace : 1; /**< if kash -c "" needs an extra space before the quote. */
1336 } *paArgInfo;
1337 size_t cArgs;
1338 size_t i;
1339 size_t cwcNeeded;
1340 WCHAR *pwszDst;
1341 WCHAR *pwszCmdLine;
1342
1343 /*
1344 * Count them first so we can allocate an info array of the stack.
1345 */
1346 cArgs = 0;
1347 while (papszArgs[cArgs] != NULL)
1348 cArgs++;
1349 paArgInfo = (struct ARGINFO *)alloca(sizeof(paArgInfo[0]) * cArgs);
1350
1351 /*
1352 * Preprocess them and calculate the exact command line length.
1353 */
1354 cwcNeeded = 1;
1355 for (i = 0; i < cArgs; i++)
1356 {
1357 char *pszSrc = papszArgs[i];
1358 size_t cchSrc = strlen(pszSrc);
1359 paArgInfo[i].cchSrc = cchSrc;
1360 if (cchSrc == 0)
1361 {
1362 /* empty needs quoting. */
1363 paArgInfo[i].cwcDst = 2;
1364 paArgInfo[i].cwcDstExtra = 0;
1365 paArgInfo[i].fSlowly = 0;
1366 paArgInfo[i].fQuoteIt = 1;
1367 paArgInfo[i].fExtraSpace = 0;
1368 paArgInfo[i].fEndSlashes = 0;
1369 }
1370 else
1371 {
1372 const char *pszSpace = memchr(pszSrc, ' ', cchSrc);
1373 const char *pszTab = memchr(pszSrc, '\t', cchSrc);
1374 const char *pszDQuote = memchr(pszSrc, '"', cchSrc);
1375 const char *pszEscape = memchr(pszSrc, '\\', cchSrc);
1376 int cwcDst = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszSrc, cchSrc + 1, NULL, 0);
1377 if (cwcDst >= 0)
1378 --cwcDst;
1379 else
1380 {
1381 DWORD dwErr = GetLastError();
1382 MkWinChildError(pWorker, 1, _("MultiByteToWideChar failed to convert argv[%u] (%s): %u\n"), i, pszSrc, dwErr);
1383 return dwErr;
1384 }
1385#if 0
1386 if (!pszSpace && !pszTab && !pszDQuote && !pszEscape)
1387 {
1388 /* no special handling needed. */
1389 paArgInfo[i].cwcDst = cwcDst;
1390 paArgInfo[i].cwcDstExtra = 0;
1391 paArgInfo[i].fSlowly = 0;
1392 paArgInfo[i].fQuoteIt = 0;
1393 paArgInfo[i].fExtraSpace = 0;
1394 paArgInfo[i].fEndSlashes = 0;
1395 }
1396 else if (!pszDQuote && !pszEscape)
1397 {
1398 /* Just double quote it. */
1399 paArgInfo[i].cwcDst = cwcDst + 2;
1400 paArgInfo[i].cwcDstExtra = 0;
1401 paArgInfo[i].fSlowly = 0;
1402 paArgInfo[i].fQuoteIt = 1;
1403 paArgInfo[i].fExtraSpace = 0;
1404 paArgInfo[i].fEndSlashes = 0;
1405 }
1406 else
1407#endif
1408 {
1409 /* Complicated, need to scan the string to figure out what to do. */
1410 size_t cwcDstExtra;
1411 int cBackslashes;
1412 char ch;
1413
1414 paArgInfo[i].fQuoteIt = 0;
1415 paArgInfo[i].fSlowly = 1;
1416 paArgInfo[i].fExtraSpace = 0;
1417 paArgInfo[i].fEndSlashes = 0;
1418
1419 cwcDstExtra = 0;
1420 cBackslashes = 0;
1421 while ((ch = *pszSrc++) != '\0')
1422 {
1423 switch (ch)
1424 {
1425 default:
1426 cBackslashes = 0;
1427 break;
1428
1429 case '\\':
1430 cBackslashes++;
1431 break;
1432
1433 case '"':
1434 if (fFlags & (MKWCCWCMD_F_CYGWIN_SHELL | MKWCCWCMD_F_MKS_SHELL))
1435 cwcDstExtra += 1; /* just an extra '"' */
1436 else
1437 cwcDstExtra += 1 + cBackslashes; /* extra '\\' for the '"' and for each preceeding slash. */
1438 cBackslashes = 0;
1439 break;
1440
1441 case ' ':
1442 case '\t':
1443 if (!paArgInfo[i].fQuoteIt)
1444 {
1445 paArgInfo[i].fQuoteIt = 1;
1446 cwcDstExtra += 2;
1447 }
1448 cBackslashes = 0;
1449 break;
1450 }
1451 }
1452
1453 /* If we're quoting the argument and it ends with trailing '\\', it/they must be escaped. */
1454 if ( cBackslashes > 0
1455 && paArgInfo[i].fQuoteIt
1456 && !(fFlags & (MKWCCWCMD_F_CYGWIN_SHELL | MKWCCWCMD_F_MKS_SHELL)))
1457 {
1458 cwcDstExtra += cBackslashes;
1459 paArgInfo[i].fEndSlashes = 1;
1460 }
1461
1462 paArgInfo[i].cwcDst = cwcDst + cwcDstExtra;
1463 paArgInfo[i].cwcDstExtra = cwcDstExtra;
1464 }
1465 }
1466
1467 if ( (fFlags & MKWCCWCMD_F_HAVE_KASH_C)
1468 && paArgInfo[i].fQuoteIt)
1469 {
1470 paArgInfo[i].fExtraSpace = 1;
1471 paArgInfo[i].cwcDst++;
1472 paArgInfo[i].cwcDstExtra++;
1473 }
1474
1475 cwcNeeded += (i != 0) + paArgInfo[i].cwcDst;
1476 }
1477
1478 /*
1479 * Allocate the result buffer and do the actual conversion.
1480 */
1481 pwszDst = pwszCmdLine = (WCHAR *)xmalloc(sizeof(WCHAR) * cwcNeeded);
1482 for (i = 0; i < cArgs; i++)
1483 {
1484 char *pszSrc = papszArgs[i];
1485 size_t cwcDst = paArgInfo[i].cwcDst;
1486
1487 if (i != 0)
1488 *pwszDst++ = L' ';
1489
1490 if (paArgInfo[i].fQuoteIt)
1491 {
1492 *pwszDst++ = L'"';
1493 cwcDst -= 2;
1494 }
1495
1496 if (!paArgInfo[i].fSlowly)
1497 {
1498 int cwcDst2 = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszSrc, paArgInfo[i].cchSrc, pwszDst, cwcDst + 1);
1499 assert(cwcDst2 >= 0);
1500 pwszDst += cwcDst;
1501 }
1502 else
1503 {
1504 /* Do the conversion into the end of the output buffer, then move
1505 it up to where it should be char by char. */
1506 int cBackslashes;
1507 size_t cwcLeft = paArgInfo[i].cwcDst - paArgInfo[i].cwcDstExtra;
1508 WCHAR volatile *pwchSlowSrc = pwszDst + paArgInfo[i].cwcDstExtra;
1509 WCHAR volatile *pwchSlowDst = pwszDst;
1510 int cwcDst2 = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszSrc, paArgInfo[i].cchSrc,
1511 (WCHAR *)pwchSlowSrc, cwcLeft + 1);
1512 assert(cwcDst2 >= 0);
1513
1514 cBackslashes = 0;
1515 while (cwcLeft-- > 0)
1516 {
1517 WCHAR wcSrc = *pwchSlowSrc++;
1518 if (wcSrc != L'\\' && wcSrc != L'"')
1519 cBackslashes = 0;
1520 else if (wcSrc == L'\\')
1521 cBackslashes++;
1522 else if ( (fFlags & (MKWCCWCMD_F_CYGWIN_SHELL | MKWCCWCMD_F_HAVE_SH))
1523 == (MKWCCWCMD_F_CYGWIN_SHELL | MKWCCWCMD_F_HAVE_SH))
1524 {
1525 *pwchSlowDst++ = L'"'; /* cygwin: '"' instead of '\\', no escaped slashes. */
1526 cBackslashes = 0;
1527 }
1528 else
1529 {
1530 if (!(fFlags & (MKWCCWCMD_F_CYGWIN_SHELL | MKWCCWCMD_F_MKS_SHELL)))
1531 cBackslashes += 1; /* one extra escape the '"' and one for each preceeding slash. */
1532 while (cBackslashes > 0)
1533 {
1534 *pwchSlowDst++ = L'\\';
1535 cBackslashes--;
1536 }
1537 }
1538 *pwchSlowDst++ = wcSrc;
1539 assert((uintptr_t)pwchSlowDst <= (uintptr_t)pwchSlowSrc);
1540 }
1541
1542 if (paArgInfo[i].fEndSlashes)
1543 while (cBackslashes-- > 0)
1544 *pwchSlowDst++ = L'\\';
1545
1546 pwszDst += cwcDst;
1547 assert(pwszDst == (WCHAR *)pwchSlowDst);
1548 }
1549
1550 if (paArgInfo[i].fExtraSpace)
1551 *pwszDst++ = L' ';
1552 if (paArgInfo[i].fQuoteIt)
1553 *pwszDst++ = L'"';
1554 }
1555 *pwszDst = L'\0';
1556 *ppwszCommandLine = pwszCmdLine;
1557 return 0;
1558}
1559
1560static int mkWinChildcareWorkerConvertCommandlineWithShell(PWINCHILDCAREWORKER pWorker, const WCHAR *pwszShell, char **papszArgs,
1561 WCHAR **ppwszCommandLine)
1562{
1563 MkWinChildError(pWorker, 1, "%s: not found!\n", papszArgs[0]);
1564//__debugbreak();
1565 return ERROR_FILE_NOT_FOUND;
1566}
1567
1568/**
1569 * Searches the environment block for the PATH variable.
1570 *
1571 * @returns Pointer to the path in the block or "." in pwszPathFallback.
1572 * @param pwszzEnv The UTF-16 environment block to search.
1573 * @param pwszPathFallback Fallback.
1574 */
1575static const WCHAR *mkWinChildcareWorkerFindPathValue(const WCHAR *pwszzEnv, WCHAR pwszPathFallback[4])
1576{
1577 while (*pwszzEnv)
1578 {
1579 size_t cwcVar = wcslen(pwszzEnv);
1580 if (!IS_PATH_ENV_VAR(cwcVar, pwszzEnv))
1581 pwszzEnv += cwcVar + 1;
1582 else if (cwcVar > 5)
1583 return &pwszzEnv[5];
1584 else
1585 break;
1586 }
1587 pwszPathFallback[0] = L'.';
1588 pwszPathFallback[1] = L'\0';
1589 return pwszPathFallback;
1590}
1591
1592/**
1593 * Checks if we need to had this executable file to the shell.
1594 *
1595 * @returns TRUE if it's shell fooder, FALSE if we think windows can handle it.
1596 * @param hFile Handle to the file in question
1597 */
1598static BOOL mkWinChildcareWorkerCheckIfNeedShell(HANDLE hFile)
1599{
1600 /*
1601 * Read the first 512 bytes and check for an executable image header.
1602 */
1603 union
1604 {
1605 DWORD dwSignature;
1606 WORD wSignature;
1607 BYTE ab[128];
1608 } uBuf;
1609 DWORD cbRead;
1610 uBuf.dwSignature = 0;
1611 if ( ReadFile(hFile, &uBuf, sizeof(uBuf), &cbRead, NULL /*pOverlapped*/)
1612 && cbRead == sizeof(uBuf))
1613 {
1614 if (uBuf.wSignature == IMAGE_DOS_SIGNATURE)
1615 return FALSE;
1616 if (uBuf.dwSignature == IMAGE_NT_SIGNATURE)
1617 return FALSE;
1618 if ( uBuf.wSignature == IMAGE_OS2_SIGNATURE /* NE */
1619 || uBuf.wSignature == 0x5d4c /* LX */
1620 || uBuf.wSignature == IMAGE_OS2_SIGNATURE_LE /* LE */)
1621 return FALSE;
1622 }
1623 return TRUE;
1624}
1625
1626/**
1627 * Checks if the image path looks like microsoft CL.exe.
1628 *
1629 * @returns TRUE / FALSE.
1630 * @param pwszImagePath The executable image path to evalutate.
1631 * @param cwcImagePath The length of the image path.
1632 */
1633static BOOL mkWinChildIsProbableClExe(WCHAR const *pwszImagePath, size_t cwcImagePath)
1634{
1635 assert(pwszImagePath[cwcImagePath] == '\0');
1636 return cwcImagePath > 7
1637 && (pwszImagePath[cwcImagePath - 7] == L'/' || pwszImagePath[cwcImagePath - 7] == L'\\')
1638 && (pwszImagePath[cwcImagePath - 6] == L'c' || pwszImagePath[cwcImagePath - 6] == L'C')
1639 && (pwszImagePath[cwcImagePath - 5] == L'l' || pwszImagePath[cwcImagePath - 5] == L'L')
1640 && pwszImagePath[cwcImagePath - 4] == L'.'
1641 && (pwszImagePath[cwcImagePath - 3] == L'e' || pwszImagePath[cwcImagePath - 3] == L'E')
1642 && (pwszImagePath[cwcImagePath - 2] == L'x' || pwszImagePath[cwcImagePath - 2] == L'X')
1643 && (pwszImagePath[cwcImagePath - 1] == L'e' || pwszImagePath[cwcImagePath - 1] == L'E');
1644}
1645
1646/**
1647 * Temporary workaround for seemingly buggy kFsCache.c / dir-nt-bird.c.
1648 *
1649 * Something is not invalidated / updated correctly!
1650 */
1651static BOOL mkWinChildcareWorkerIsRegularFileW(PWINCHILDCAREWORKER pWorker, wchar_t const *pwszPath)
1652{
1653 BOOL fRet = FALSE;
1654#ifdef KMK
1655 if (utf16_regular_file_p(pwszPath))
1656 fRet = TRUE;
1657 else
1658#endif
1659 {
1660 /* Don't believe the cache. */
1661 DWORD dwAttr = GetFileAttributesW(pwszPath);
1662 if (dwAttr != INVALID_FILE_ATTRIBUTES)
1663 {
1664 if (!(dwAttr & FILE_ATTRIBUTE_DIRECTORY))
1665 {
1666#ifdef KMK
1667 extern void dir_cache_invalid_volatile(void);
1668 dir_cache_invalid_volatile();
1669 if (utf16_regular_file_p(pwszPath))
1670 MkWinChildError(pWorker, 1, "kFsCache was out of sync! pwszPath=%S\n", pwszPath);
1671 else
1672 {
1673 dir_cache_invalid_all();
1674 if (utf16_regular_file_p(pwszPath))
1675 MkWinChildError(pWorker, 1, "kFsCache was really out of sync! pwszPath=%S\n", pwszPath);
1676 else
1677 MkWinChildError(pWorker, 1, "kFsCache is really out of sync!! pwszPath=%S\n", pwszPath);
1678 }
1679#endif
1680 fRet = TRUE;
1681 }
1682 }
1683 }
1684 return fRet;
1685}
1686
1687
1688/**
1689 * Tries to locate the image file, searching the path and maybe falling back on
1690 * the shell in case it knows more (think cygwin with its own view of the file
1691 * system).
1692 *
1693 * This will also check for shell script, falling back on the shell too to
1694 * handle those.
1695 *
1696 * @returns 0 on success, windows error code on failure.
1697 * @param pWorker The childcare worker.
1698 * @param pszArg0 The first argument.
1699 * @param pwszSearchPath In case mkWinChildcareWorkerConvertEnvironment
1700 * had a chance of locating the search path already.
1701 * @param pwszzEnv The environment block, in case we need to look for
1702 * the path.
1703 * @param pszShell The shell.
1704 * @param ppwszImagePath Where to return the pointer to the image path. This
1705 * could be the shell.
1706 * @param pfNeedShell Where to return shell vs direct execution indicator.
1707 * @param pfProbableClExe Where to return an indicator of probably CL.EXE.
1708 */
1709static int mkWinChildcareWorkerFindImage(PWINCHILDCAREWORKER pWorker, char const *pszArg0, WCHAR *pwszSearchPath,
1710 WCHAR const *pwszzEnv, const char *pszShell,
1711 WCHAR **ppwszImagePath, BOOL *pfNeedShell, BOOL *pfProbableClExe)
1712{
1713 /** @todo Slap a cache on this code. We usually end up executing the same
1714 * stuff over and over again (e.g. compilers, linkers, etc).
1715 * Hitting the file system is slow on windows. */
1716
1717 /*
1718 * Convert pszArg0 to unicode so we can work directly on that.
1719 */
1720 WCHAR wszArg0[MKWINCHILD_MAX_PATH + 4]; /* +4 for painless '.exe' appending */
1721 DWORD dwErr;
1722 size_t cbArg0 = strlen(pszArg0) + 1;
1723 int const cwcArg0 = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszArg0, cbArg0, wszArg0, MKWINCHILD_MAX_PATH);
1724 if (cwcArg0 > 0)
1725 {
1726 HANDLE hFile = INVALID_HANDLE_VALUE;
1727 WCHAR wszPathBuf[MKWINCHILD_MAX_PATH + 4]; /* +4 for painless '.exe' appending */
1728 int cwc;
1729
1730 /*
1731 * If there isn't an .exe suffix, we may have to add one.
1732 * Also we ASSUME that .exe suffixes means no hash bang detection needed.
1733 */
1734 int const fHasExeSuffix = cwcArg0 > CSTRLEN(".exe")
1735 && wszArg0[cwcArg0 - 4] == '.'
1736 && (wszArg0[cwcArg0 - 3] == L'e' || wszArg0[cwcArg0 - 3] == L'E')
1737 && (wszArg0[cwcArg0 - 2] == L'x' || wszArg0[cwcArg0 - 2] == L'X')
1738 && (wszArg0[cwcArg0 - 1] == L'e' || wszArg0[cwcArg0 - 1] == L'E');
1739
1740 /*
1741 * If there isn't any path specified, we need to search the PATH env.var.
1742 */
1743 int const fHasPath = wszArg0[1] == L':'
1744 || wszArg0[0] == L'\\'
1745 || wszArg0[0] == L'/'
1746 || wmemchr(wszArg0, L'/', cwcArg0)
1747 || wmemchr(wszArg0, L'\\', cwcArg0);
1748
1749 /* Before we do anything, flip UNIX slashes to DOS ones. */
1750 WCHAR *pwc = wszArg0;
1751 while ((pwc = wcschr(pwc, L'/')) != NULL)
1752 *pwc++ = L'\\';
1753
1754 /* Don't need to set these all the time... */
1755 *pfNeedShell = FALSE;
1756 *pfProbableClExe = FALSE;
1757
1758 /*
1759 * If any kind of path is specified in arg0, we will not search the
1760 * PATH env.var and can limit ourselves to maybe slapping a .exe on to it.
1761 */
1762 if (fHasPath)
1763 {
1764 /*
1765 * If relative to a CWD, turn it into an absolute one.
1766 */
1767 unsigned cwcPath = cwcArg0;
1768 WCHAR *pwszPath = wszArg0;
1769 if ( *pwszPath != L'\\'
1770 && (pwszPath[1] != ':' || pwszPath[2] != L'\\') )
1771 {
1772 DWORD cwcAbsPath = GetFullPathNameW(wszArg0, MKWINCHILD_MAX_PATH, wszPathBuf, NULL);
1773 if (cwcAbsPath > 0)
1774 {
1775 cwcPath = cwcAbsPath + 1; /* include terminator, like MultiByteToWideChar does. */
1776 pwszPath = wszPathBuf;
1777 }
1778 }
1779
1780 /*
1781 * Check with .exe suffix first.
1782 * We don't open .exe files and look for hash bang stuff, we just
1783 * assume they are executable images that CreateProcess can deal with.
1784 */
1785 if (!fHasExeSuffix)
1786 {
1787 pwszPath[cwcPath - 1] = L'.';
1788 pwszPath[cwcPath ] = L'e';
1789 pwszPath[cwcPath + 1] = L'x';
1790 pwszPath[cwcPath + 2] = L'e';
1791 pwszPath[cwcPath + 3] = L'\0';
1792 }
1793
1794 if (mkWinChildcareWorkerIsRegularFileW(pWorker, pwszPath))
1795 {
1796 *pfProbableClExe = mkWinChildIsProbableClExe(pwszPath, cwcPath + 4 - 1);
1797 return mkWinChildDuplicateUtf16String(pwszPath, cwcPath + 4, ppwszImagePath);
1798 }
1799
1800 /*
1801 * If no suffix was specified, try without .exe too, but now we need
1802 * to see if it's for the shell or CreateProcess.
1803 */
1804 if (!fHasExeSuffix)
1805 {
1806 pwszPath[cwcPath - 1] = L'\0';
1807#ifdef KMK
1808 if (mkWinChildcareWorkerIsRegularFileW(pWorker, pwszPath))
1809#endif
1810 {
1811 hFile = CreateFileW(pwszPath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
1812 NULL /*pSecAttr*/, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1813 if (hFile != INVALID_HANDLE_VALUE)
1814 {
1815 *pfNeedShell = mkWinChildcareWorkerCheckIfNeedShell(hFile);
1816 CloseHandle(hFile);
1817 if (!*pfNeedShell)
1818 {
1819 *pfProbableClExe = mkWinChildIsProbableClExe(pwszPath, cwcPath - 1);
1820 return mkWinChildDuplicateUtf16String(pwszPath, cwcPath, ppwszImagePath);
1821 }
1822 }
1823 }
1824 }
1825 }
1826 /*
1827 * No path, need to search the PATH env.var. for the executable, maybe
1828 * adding an .exe suffix while do so if that is missing.
1829 */
1830 else
1831 {
1832 BOOL fSearchedCwd = FALSE;
1833 WCHAR wszPathFallback[4];
1834 if (!pwszSearchPath)
1835 pwszSearchPath = (WCHAR *)mkWinChildcareWorkerFindPathValue(pwszzEnv, wszPathFallback);
1836
1837 for (;;)
1838 {
1839 size_t cwcCombined;
1840
1841 /*
1842 * Find the end of the current PATH component.
1843 */
1844 size_t cwcSkip;
1845 WCHAR wcEnd;
1846 size_t cwcComponent = 0;
1847 WCHAR wc;
1848 while ((wc = pwszSearchPath[cwcComponent]) != L'\0')
1849 {
1850 if (wc != ';' && wc != ':')
1851 { /* likely */ }
1852 else if (wc == ';')
1853 break;
1854 else if (cwcComponent != (pwszSearchPath[cwcComponent] != L'"' ? 1 : 2))
1855 break;
1856 cwcComponent++;
1857 }
1858 wcEnd = wc;
1859
1860 /* Trim leading spaces and double quotes. */
1861 while ( cwcComponent > 0
1862 && ((wc = *pwszSearchPath) == L'"' || wc == L' ' || wc == L'\t'))
1863 {
1864 pwszSearchPath++;
1865 cwcComponent--;
1866 }
1867 cwcSkip = cwcComponent;
1868
1869 /* Trim trailing spaces & double quotes. */
1870 while ( cwcComponent > 0
1871 && ((wc = pwszSearchPath[cwcComponent - 1]) == L'"' || wc == L' ' || wc == L'\t'))
1872 cwcComponent--;
1873
1874 /*
1875 * Skip empty components. Join the component and the filename, making sure to
1876 * resolve any CWD relative stuff first.
1877 */
1878 cwcCombined = cwcComponent + 1 + cwcArg0;
1879 if (cwcComponent > 0 && cwcCombined <= MKWINCHILD_MAX_PATH)
1880 {
1881 /* Copy the component into wszPathBuf, maybe abspath'ing it. */
1882 DWORD cwcAbsPath = 0;
1883 if ( *pwszSearchPath != L'\\'
1884 && (pwszSearchPath[1] != ':' || pwszSearchPath[2] != L'\\') )
1885 {
1886 /* To save an extra buffer + copying, we'll temporarily modify the PATH
1887 value in our converted UTF-16 environment block. */
1888 WCHAR const wcSaved = pwszSearchPath[cwcComponent];
1889 pwszSearchPath[cwcComponent] = L'\0';
1890 cwcAbsPath = GetFullPathNameW(pwszSearchPath, MKWINCHILD_MAX_PATH, wszPathBuf, NULL);
1891 pwszSearchPath[cwcComponent] = wcSaved;
1892 if (cwcAbsPath > 0 && cwcAbsPath + 1 + cwcArg0 <= MKWINCHILD_MAX_PATH)
1893 cwcCombined = cwcAbsPath + 1 + cwcArg0;
1894 else
1895 cwcAbsPath = 0;
1896 }
1897 if (cwcAbsPath == 0)
1898 {
1899 memcpy(wszPathBuf, pwszSearchPath, cwcComponent * sizeof(WCHAR));
1900 cwcAbsPath = cwcComponent;
1901 }
1902
1903 /* Append the filename. */
1904 if ((wc = wszPathBuf[cwcAbsPath - 1]) == L'\\' || wc == L'/' || wc == L':')
1905 {
1906 memcpy(&wszPathBuf[cwcAbsPath], wszArg0, cwcArg0 * sizeof(WCHAR));
1907 cwcCombined--;
1908 }
1909 else
1910 {
1911 wszPathBuf[cwcAbsPath] = L'\\';
1912 memcpy(&wszPathBuf[cwcAbsPath + 1], wszArg0, cwcArg0 * sizeof(WCHAR));
1913 }
1914 assert(wszPathBuf[cwcCombined - 1] == L'\0');
1915
1916 /* DOS slash conversion */
1917 pwc = wszPathBuf;
1918 while ((pwc = wcschr(pwc, L'/')) != NULL)
1919 *pwc++ = L'\\';
1920
1921 /*
1922 * Search with exe suffix first.
1923 */
1924 if (!fHasExeSuffix)
1925 {
1926 wszPathBuf[cwcCombined - 1] = L'.';
1927 wszPathBuf[cwcCombined ] = L'e';
1928 wszPathBuf[cwcCombined + 1] = L'x';
1929 wszPathBuf[cwcCombined + 2] = L'e';
1930 wszPathBuf[cwcCombined + 3] = L'\0';
1931 }
1932 if (mkWinChildcareWorkerIsRegularFileW(pWorker, wszPathBuf))
1933 {
1934 *pfProbableClExe = mkWinChildIsProbableClExe(wszPathBuf, cwcCombined + (fHasExeSuffix ? 0 : 4) - 1);
1935 return mkWinChildDuplicateUtf16String(wszPathBuf, cwcCombined + (fHasExeSuffix ? 0 : 4), ppwszImagePath);
1936 }
1937 if (!fHasExeSuffix)
1938 {
1939 wszPathBuf[cwcCombined - 1] = L'\0';
1940#ifdef KMK
1941 if (mkWinChildcareWorkerIsRegularFileW(pWorker, wszPathBuf))
1942#endif
1943 {
1944 /*
1945 * Check if the file exists w/o the added '.exe' suffix. If it does,
1946 * we need to check if we can pass it to CreateProcess or need the shell.
1947 */
1948 hFile = CreateFileW(wszPathBuf, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
1949 NULL /*pSecAttr*/, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1950 if (hFile != INVALID_HANDLE_VALUE)
1951 {
1952 *pfNeedShell = mkWinChildcareWorkerCheckIfNeedShell(hFile);
1953 CloseHandle(hFile);
1954 if (!*pfNeedShell)
1955 {
1956 *pfProbableClExe = mkWinChildIsProbableClExe(wszPathBuf, cwcCombined - 1);
1957 return mkWinChildDuplicateUtf16String(wszPathBuf, cwcCombined, ppwszImagePath);
1958 }
1959 break;
1960 }
1961 }
1962 }
1963 }
1964
1965 /*
1966 * Advance to the next component.
1967 */
1968 if (wcEnd != '\0')
1969 pwszSearchPath += cwcSkip + 1;
1970 else if (fSearchedCwd)
1971 break;
1972 else
1973 {
1974 fSearchedCwd = TRUE;
1975 wszPathFallback[0] = L'.';
1976 wszPathFallback[1] = L'\0';
1977 pwszSearchPath = wszPathFallback;
1978 }
1979 }
1980 }
1981
1982 /*
1983 * We need the shell. It will take care of finding/reporting missing
1984 * image files and such.
1985 */
1986 *pfNeedShell = TRUE;
1987 if (pszShell)
1988 {
1989 cwc = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszShell, strlen(pszShell) + 1, wszPathBuf, MKWINCHILD_MAX_PATH);
1990 if (cwc > 0)
1991 return mkWinChildDuplicateUtf16String(wszPathBuf, cwc, ppwszImagePath);
1992 dwErr = GetLastError();
1993 MkWinChildError(pWorker, 1, _("MultiByteToWideChar failed to convert shell (%s): %u\n"), pszShell, dwErr);
1994 }
1995 else
1996 {
1997 MkWinChildError(pWorker, 1, "%s: not found!\n", pszArg0);
1998 dwErr = ERROR_FILE_NOT_FOUND;
1999 }
2000 }
2001 else
2002 {
2003 dwErr = GetLastError();
2004 MkWinChildError(pWorker, 1, _("MultiByteToWideChar failed to convert argv[0] (%s): %u\n"), pszArg0, dwErr);
2005 }
2006 return dwErr == ERROR_INSUFFICIENT_BUFFER ? ERROR_FILENAME_EXCED_RANGE : dwErr;
2007}
2008
2009/**
2010 * Creates the environment block.
2011 *
2012 * @returns 0 on success, windows error code on failure.
2013 * @param pWorker The childcare worker if on one, otherwise NULL.
2014 * @param papszEnv The environment vector to convert.
2015 * @param cbEnvStrings The size of the environment strings, iff they are
2016 * sequential in a block. Otherwise, zero.
2017 * @param ppwszEnv Where to return the pointer to the environment
2018 * block.
2019 * @param ppwszSearchPath Where to return the pointer to the path value
2020 * within the environment block. This will not be set
2021 * if cbEnvStrings is non-zero, more efficient to let
2022 * mkWinChildcareWorkerFindImage() search when needed.
2023 */
2024static int mkWinChildcareWorkerConvertEnvironment(PWINCHILDCAREWORKER pWorker, char **papszEnv, size_t cbEnvStrings,
2025 WCHAR **ppwszEnv, WCHAR const **ppwszSearchPath)
2026{
2027 DWORD dwErr;
2028 int cwcRc;
2029 int cwcDst;
2030 WCHAR *pwszzDst;
2031
2032 *ppwszSearchPath = NULL;
2033
2034 /*
2035 * We've got a little optimization here with help from mkWinChildCopyStringArray.
2036 */
2037 if (cbEnvStrings)
2038 {
2039 cwcDst = cbEnvStrings + 32;
2040 pwszzDst = (WCHAR *)xmalloc(cwcDst * sizeof(WCHAR));
2041 cwcRc = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, papszEnv[0], cbEnvStrings, pwszzDst, cwcDst);
2042 if (cwcRc != 0)
2043 {
2044 *ppwszEnv = pwszzDst;
2045 return 0;
2046 }
2047
2048 /* Resize the allocation and try again. */
2049 dwErr = GetLastError();
2050 if (dwErr == ERROR_INSUFFICIENT_BUFFER)
2051 {
2052 cwcRc = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, papszEnv[0], cbEnvStrings, NULL, 0);
2053 if (cwcRc > 0)
2054 cwcDst = cwcRc + 32;
2055 else
2056 cwcDst *= 2;
2057 pwszzDst = (WCHAR *)xrealloc(pwszzDst, cwcDst);
2058 cwcRc = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, papszEnv[0], cbEnvStrings, pwszzDst, cwcDst);
2059 if (cwcRc != 0)
2060 {
2061 *ppwszEnv = pwszzDst;
2062 return 0;
2063 }
2064 dwErr = GetLastError();
2065 }
2066 MkWinChildError(pWorker, 1, _("MultiByteToWideChar failed to convert environment block: %u\n"), dwErr);
2067 }
2068 /*
2069 * Need to convert it string by string.
2070 */
2071 else
2072 {
2073 size_t offPathValue = ~(size_t)0;
2074 size_t offDst;
2075
2076 /*
2077 * Estimate the size first.
2078 */
2079 size_t cEnvVars;
2080 size_t cwcDst = 32;
2081 size_t iVar = 0;
2082 const char *pszSrc;
2083 while ((pszSrc = papszEnv[iVar]) != NULL)
2084 {
2085 cwcDst += strlen(pszSrc) + 1;
2086 iVar++;
2087 }
2088 cEnvVars = iVar;
2089
2090 /* Allocate estimated WCHARs and convert the variables one by one, reallocating
2091 the block as needed. */
2092 pwszzDst = (WCHAR *)xmalloc(cwcDst * sizeof(WCHAR));
2093 cwcDst--; /* save one wchar for the terminating empty string. */
2094 offDst = 0;
2095 for (iVar = 0; iVar < cEnvVars; iVar++)
2096 {
2097 size_t cwcLeft = cwcDst - offDst;
2098 size_t const cbSrc = strlen(pszSrc = papszEnv[iVar]) + 1;
2099 assert(cwcDst >= offDst);
2100
2101
2102 cwcRc = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszSrc, cbSrc, &pwszzDst[offDst], cwcLeft);
2103 if (cwcRc > 0)
2104 { /* likely */ }
2105 else
2106 {
2107 dwErr = GetLastError();
2108 if (dwErr == ERROR_INSUFFICIENT_BUFFER)
2109 {
2110 /* Need more space. So, calc exacly how much and resize the block accordingly. */
2111 size_t cbSrc2 = cbSrc;
2112 size_t iVar2 = iVar;
2113 cwcLeft = 1;
2114 for (;;)
2115 {
2116 size_t cwcRc2 = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszSrc, cbSrc, NULL, 0);
2117 if (cwcRc2 > 0)
2118 cwcLeft += cwcRc2;
2119 else
2120 cwcLeft += cbSrc * 4;
2121
2122 /* advance */
2123 iVar2++;
2124 if (iVar2 >= cEnvVars)
2125 break;
2126 pszSrc = papszEnv[iVar2];
2127 cbSrc2 = strlen(pszSrc) + 1;
2128 }
2129 pszSrc = papszEnv[iVar];
2130
2131 /* Grow the allocation and repeat the conversion. */
2132 if (offDst + cwcLeft > cwcDst + 1)
2133 {
2134 cwcDst = offDst + cwcLeft;
2135 pwszzDst = (WCHAR *)xrealloc(pwszzDst, cwcDst * sizeof(WCHAR));
2136 cwcDst--; /* save one wchar for the terminating empty string. */
2137 cwcRc = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszSrc, cbSrc, &pwszzDst[offDst], cwcLeft - 1);
2138 if (cwcRc <= 0)
2139 dwErr = GetLastError();
2140 }
2141 }
2142 if (cwcRc <= 0)
2143 {
2144 MkWinChildError(pWorker, 1, _("MultiByteToWideChar failed to convert environment string #%u (%s): %u\n"),
2145 iVar, pszSrc, dwErr);
2146 free(pwszzDst);
2147 return dwErr;
2148 }
2149 }
2150
2151 /* Look for the PATH. */
2152 if ( offPathValue == ~(size_t)0
2153 && IS_PATH_ENV_VAR(cwcRc, &pwszzDst[offDst]) )
2154 offPathValue = offDst + 4 + 1;
2155
2156 /* Advance. */
2157 offDst += cwcRc;
2158 }
2159 pwszzDst[offDst++] = '\0';
2160
2161 if (offPathValue != ~(size_t)0)
2162 *ppwszSearchPath = &pwszzDst[offPathValue];
2163 *ppwszEnv = pwszzDst;
2164 return 0;
2165 }
2166 free(pwszzDst);
2167 return dwErr;
2168}
2169
2170/**
2171 * Childcare worker: handle regular process.
2172 *
2173 * @param pWorker The worker.
2174 * @param pChild The kSubmit child.
2175 */
2176static void mkWinChildcareWorkerThreadHandleProcess(PWINCHILDCAREWORKER pWorker, PWINCHILD pChild)
2177{
2178 WCHAR *pwszSearchPath = NULL;
2179 WCHAR *pwszzEnvironment = NULL;
2180 WCHAR *pwszCommandLine = NULL;
2181 WCHAR *pwszImageName = NULL;
2182 BOOL fNeedShell = FALSE;
2183 BOOL fProbableClExe = FALSE;
2184 int rc;
2185
2186 /*
2187 * First we convert the environment so we get the PATH we need to
2188 * search for the executable.
2189 */
2190 rc = mkWinChildcareWorkerConvertEnvironment(pWorker, pChild->u.Process.papszEnv ? pChild->u.Process.papszEnv : environ,
2191 pChild->u.Process.cbEnvStrings,
2192 &pwszzEnvironment, &pwszSearchPath);
2193 /*
2194 * Find the executable and maybe checking if it's a shell script, then
2195 * convert it to a command line.
2196 */
2197 if (rc == 0)
2198 rc = mkWinChildcareWorkerFindImage(pWorker, pChild->u.Process.papszArgs[0], pwszSearchPath, pwszzEnvironment,
2199 pChild->u.Process.pszShell, &pwszImageName, &fNeedShell, &pChild->fProbableClExe);
2200 if (rc == 0)
2201 {
2202 if (!fNeedShell)
2203 rc = mkWinChildcareWorkerConvertCommandline(pWorker, pChild->u.Process.papszArgs, 0 /*fFlags*/, &pwszCommandLine);
2204 else
2205 rc = mkWinChildcareWorkerConvertCommandlineWithShell(pWorker, pwszImageName, pChild->u.Process.papszArgs,
2206 &pwszCommandLine);
2207
2208 /*
2209 * Create the child process.
2210 */
2211 if (rc == 0)
2212 {
2213 BOOL afReplace[3] = { FALSE, pChild->u.Process.hStdOut != INVALID_HANDLE_VALUE, pChild->u.Process.hStdErr != INVALID_HANDLE_VALUE };
2214 HANDLE ahChild[3] = { INVALID_HANDLE_VALUE, pChild->u.Process.hStdOut, pChild->u.Process.hStdErr };
2215 rc = mkWinChildcareWorkerCreateProcess(pWorker, pwszImageName, pwszCommandLine, pwszzEnvironment,
2216 NULL /*pwszCwd*/, afReplace, ahChild, pChild->u.Process.fCatchOutput,
2217 &pChild->u.Process.hProcess);
2218 mkWinChildcareWorkerCloseStandardHandles(pChild);
2219 if (rc == 0)
2220 {
2221 /*
2222 * Wait for the child to complete.
2223 */
2224 mkWinChildcareWorkerWaitForProcess(pWorker, pChild, pChild->u.Process.hProcess, pwszImageName,
2225 pChild->u.Process.fCatchOutput);
2226 }
2227 else
2228 pChild->iExitCode = rc;
2229 }
2230 else
2231 pChild->iExitCode = rc;
2232 }
2233 else
2234 pChild->iExitCode = rc;
2235 free(pwszCommandLine);
2236 free(pwszImageName);
2237 free(pwszzEnvironment);
2238
2239 /* In case we failed, we must make sure the child end of pipes
2240 used by $(shell no_such_command.exe) are closed, otherwise
2241 the main thread will be stuck reading the parent end. */
2242 mkWinChildcareWorkerCloseStandardHandles(pChild);
2243}
2244
2245#ifdef KMK
2246
2247/**
2248 * Childcare worker: handle builtin command.
2249 *
2250 * @param pWorker The worker.
2251 * @param pChild The kSubmit child.
2252 */
2253static void mkWinChildcareWorkerThreadHandleBuiltIn(PWINCHILDCAREWORKER pWorker, PWINCHILD pChild)
2254{
2255 PCKMKBUILTINENTRY pBuiltIn = pChild->u.BuiltIn.pBuiltIn;
2256 KMKBUILTINCTX Ctx =
2257 {
2258 pBuiltIn->uName.s.sz,
2259 pChild->pMkChild ? &pChild->pMkChild->output : NULL,
2260 pWorker,
2261 };
2262 if (pBuiltIn->uFnSignature == FN_SIG_MAIN)
2263 pChild->iExitCode = pBuiltIn->u.pfnMain(pChild->u.BuiltIn.cArgs, pChild->u.BuiltIn.papszArgs,
2264 pChild->u.BuiltIn.papszEnv, &Ctx);
2265 else if (pBuiltIn->uFnSignature == FN_SIG_MAIN_SPAWNS)
2266 pChild->iExitCode = pBuiltIn->u.pfnMainSpawns(pChild->u.BuiltIn.cArgs, pChild->u.BuiltIn.papszArgs,
2267 pChild->u.BuiltIn.papszEnv, &Ctx, pChild->pMkChild, NULL /*pPid*/);
2268 else
2269 {
2270 assert(0);
2271 pChild->iExitCode = 98;
2272 }
2273}
2274
2275/**
2276 * Childcare worker: handle append write-out.
2277 *
2278 * @param pWorker The worker.
2279 * @param pChild The kSubmit child.
2280 */
2281static void mkWinChildcareWorkerThreadHandleAppend(PWINCHILDCAREWORKER pWorker, PWINCHILD pChild)
2282{
2283 int fd = open(pChild->u.Append.pszFilename,
2284 pChild->u.Append.fTruncate
2285 ? O_WRONLY | O_TRUNC | O_CREAT | _O_NOINHERIT | _O_BINARY
2286 : O_WRONLY | O_APPEND | O_CREAT | _O_NOINHERIT | _O_BINARY,
2287 0666);
2288 if (fd >= 0)
2289 {
2290 ssize_t cbWritten = write(fd, pChild->u.Append.pszAppend, pChild->u.Append.cbAppend);
2291 if (cbWritten == (ssize_t)pChild->u.Append.cbAppend)
2292 {
2293 if (close(fd) >= 0)
2294 {
2295 pChild->iExitCode = 0;
2296 return;
2297 }
2298 MkWinChildError(pWorker, 1, "kmk_builtin_append: close failed on '%s': %u (%s)\n",
2299 pChild->u.Append.pszFilename, errno, strerror(errno));
2300 }
2301 else
2302 MkWinChildError(pWorker, 1, "kmk_builtin_append: error writing %lu bytes to on '%s': %u (%s)\n",
2303 pChild->u.Append.cbAppend, pChild->u.Append.pszFilename, errno, strerror(errno));
2304 close(fd);
2305 }
2306 else
2307 MkWinChildError(pWorker, 1, "kmk_builtin_append: error opening '%s': %u (%s)\n",
2308 pChild->u.Append.pszFilename, errno, strerror(errno));
2309 pChild->iExitCode = 1;
2310}
2311
2312/**
2313 * Childcare worker: handle kSubmit job.
2314 *
2315 * @param pWorker The worker.
2316 * @param pChild The kSubmit child.
2317 */
2318static void mkWinChildcareWorkerThreadHandleSubmit(PWINCHILDCAREWORKER pWorker, PWINCHILD pChild)
2319{
2320 void *pvSubmitWorker = pChild->u.Submit.pvSubmitWorker;
2321
2322 /*
2323 * Prep the wait handles.
2324 */
2325 HANDLE ahHandles[3] = { pChild->u.Submit.hEvent, NULL, NULL };
2326 DWORD cHandles = 1;
2327 if (pChild->u.Submit.pStdOut)
2328 {
2329 assert(pChild->u.Submit.pStdErr);
2330 pChild->u.Submit.pStdOut->fHaveWrittenOut = FALSE;
2331 ahHandles[cHandles++] = pChild->u.Submit.pStdOut->hEvent;
2332 pChild->u.Submit.pStdErr->fHaveWrittenOut = FALSE;
2333 ahHandles[cHandles++] = pChild->u.Submit.pStdErr->hEvent;
2334 }
2335
2336 /*
2337 * Wait loop.
2338 */
2339 for (;;)
2340 {
2341 int iExitCode = -42;
2342 int iSignal = -1;
2343 DWORD dwStatus;
2344 if (cHandles == 1)
2345 dwStatus = WaitForSingleObject(ahHandles[0], INFINITE);
2346 else
2347 {
2348 dwStatus = WaitForMultipleObjects(cHandles, ahHandles, FALSE /*fWaitAll*/, INFINITE);
2349 assert(dwStatus != WAIT_FAILED);
2350 if (dwStatus == WAIT_OBJECT_0 + 1)
2351 mkWinChildcareWorkerCatchOutput(pChild, pChild->u.Submit.pStdOut, FALSE /*fDraining*/);
2352 else if (dwStatus == WAIT_OBJECT_0 + 2)
2353 mkWinChildcareWorkerCatchOutput(pChild, pChild->u.Submit.pStdErr, FALSE /*fDraining*/);
2354 }
2355 if (kSubmitSubProcGetResult((intptr_t)pvSubmitWorker, dwStatus == WAIT_OBJECT_0 /*fBlock*/, &iExitCode, &iSignal) == 0)
2356 {
2357 if (pChild->u.Submit.pStdOut)
2358 MkWinChildcareWorkerDrainPipes(pChild, pChild->u.Submit.pStdOut, pChild->u.Submit.pStdErr);
2359
2360 pChild->iExitCode = iExitCode;
2361 pChild->iSignal = iSignal;
2362 /* Cleanup must be done on the main thread. */
2363 return;
2364 }
2365
2366 if (pChild->iSignal != 0)
2367 kSubmitSubProcKill((intptr_t)pvSubmitWorker, pChild->iSignal);
2368 }
2369}
2370
2371/**
2372 * Childcare worker: handle kmk_redirect process.
2373 *
2374 * @param pWorker The worker.
2375 * @param pChild The redirect child.
2376 */
2377static void mkWinChildcareWorkerThreadHandleRedirect(PWINCHILDCAREWORKER pWorker, PWINCHILD pChild)
2378{
2379 mkWinChildcareWorkerWaitForProcess(pWorker, pChild, pChild->u.Redirect.hProcess, L"kmk_redirect", FALSE /*fCatchOutput*/);
2380}
2381
2382#endif /* KMK */
2383
2384/**
2385 * Childcare worker thread.
2386 *
2387 * @returns 0
2388 * @param pvUser The worker instance.
2389 */
2390static unsigned int __stdcall mkWinChildcareWorkerThread(void *pvUser)
2391{
2392 PWINCHILDCAREWORKER pWorker = (PWINCHILDCAREWORKER)pvUser;
2393 assert(pWorker->uMagic == WINCHILDCAREWORKER_MAGIC);
2394
2395#ifdef MKWINCHILD_DO_SET_PROCESSOR_GROUP
2396 /*
2397 * Adjust process group if necessary.
2398 *
2399 * Note! It seems that setting the mask to zero means that we select all
2400 * active processors. Couldn't find any alternative API for getting
2401 * the correct active processor mask.
2402 */
2403 if (g_cProcessorGroups > 1)
2404 {
2405 GROUP_AFFINITY Affinity = { 0 /* == all active apparently */ , pWorker->iProcessorGroup, { 0, 0, 0 } };
2406 BOOL fRet = g_pfnSetThreadGroupAffinity(GetCurrentThread(), &Affinity, NULL);
2407 assert(fRet); (void)fRet;
2408# ifndef NDEBUG
2409 {
2410 GROUP_AFFINITY ActualAffinity = { 0xbeefbeefU, 0xbeef, { 0xbeef, 0xbeef, 0xbeef } };
2411 fRet = GetThreadGroupAffinity(GetCurrentThread(), &ActualAffinity);
2412 assert(fRet); (void)fRet;
2413 assert(ActualAffinity.Group == pWorker->iProcessorGroup);
2414 }
2415# endif
2416 }
2417#endif
2418
2419 /*
2420 * Work loop.
2421 */
2422 while (!g_fShutdown)
2423 {
2424 /*
2425 * Try go idle.
2426 */
2427 PWINCHILD pChild = pWorker->pTailTodoChildren;
2428 if (!pChild)
2429 {
2430 _InterlockedExchange(&pWorker->fIdle, TRUE);
2431 pChild = pWorker->pTailTodoChildren;
2432 if (!pChild)
2433 {
2434 DWORD dwStatus;
2435
2436 _InterlockedIncrement((long *)&g_cIdleChildcareWorkers);
2437 _InterlockedExchange((long *)&g_idxLastChildcareWorker, pWorker->idxWorker);
2438 dwStatus = WaitForSingleObject(pWorker->hEvtIdle, INFINITE);
2439 _InterlockedExchange(&pWorker->fIdle, FALSE);
2440 _InterlockedDecrement((long *)&g_cIdleChildcareWorkers);
2441
2442 assert(dwStatus != WAIT_FAILED);
2443 if (dwStatus == WAIT_FAILED)
2444 Sleep(20);
2445
2446 pChild = pWorker->pTailTodoChildren;
2447 }
2448 else
2449 _InterlockedExchange(&pWorker->fIdle, FALSE);
2450 }
2451 if (pChild)
2452 {
2453 /*
2454 * We got work to do. First job is to deque the job.
2455 */
2456 pChild = mkWinChildDequeFromLifo(&pWorker->pTailTodoChildren, pChild);
2457 assert(pChild);
2458 if (pChild)
2459 {
2460 PWINCHILD pTailExpect;
2461
2462 pChild->pWorker = pWorker;
2463 pWorker->pCurChild = pChild;
2464 switch (pChild->enmType)
2465 {
2466 case WINCHILDTYPE_PROCESS:
2467 mkWinChildcareWorkerThreadHandleProcess(pWorker, pChild);
2468 break;
2469#ifdef KMK
2470 case WINCHILDTYPE_BUILT_IN:
2471 mkWinChildcareWorkerThreadHandleBuiltIn(pWorker, pChild);
2472 break;
2473 case WINCHILDTYPE_APPEND:
2474 mkWinChildcareWorkerThreadHandleAppend(pWorker, pChild);
2475 break;
2476 case WINCHILDTYPE_SUBMIT:
2477 mkWinChildcareWorkerThreadHandleSubmit(pWorker, pChild);
2478 break;
2479 case WINCHILDTYPE_REDIRECT:
2480 mkWinChildcareWorkerThreadHandleRedirect(pWorker, pChild);
2481 break;
2482#endif
2483 default:
2484 assert(0);
2485 }
2486 pWorker->pCurChild = NULL;
2487 pChild->pWorker = NULL;
2488
2489 /*
2490 * Move the child to the completed list.
2491 */
2492 pTailExpect = NULL;
2493 for (;;)
2494 {
2495 PWINCHILD pTailActual;
2496 pChild->pNext = pTailExpect;
2497 pTailActual = _InterlockedCompareExchangePointer(&g_pTailCompletedChildren, pChild, pTailExpect);
2498 if (pTailActual != pTailExpect)
2499 pTailExpect = pTailActual;
2500 else
2501 {
2502 _InterlockedDecrement(&g_cPendingChildren);
2503 if (pTailExpect)
2504 break;
2505 if (SetEvent(g_hEvtWaitChildren))
2506 break;
2507 MkWinChildError(pWorker, 1, "SetEvent(g_hEvtWaitChildren=%p) failed: %u\n",
2508 g_hEvtWaitChildren, GetLastError());
2509 break;
2510 }
2511 }
2512 }
2513 }
2514 }
2515
2516 _endthreadex(0);
2517 return 0;
2518}
2519
2520/**
2521 * Creates a pipe for catching child output.
2522 *
2523 * This is a custom CreatePipe implementation that allows for overlapped I/O on
2524 * our end of the pipe. Silly that they don't offer an API that does this.
2525 *
2526 * @returns The pipe that was created. NULL on failure.
2527 * @param pPipe The structure for the pipe.
2528 * @param iWhich Which standard descriptor this is a pipe for.
2529 * @param idxWorker The worker index.
2530 */
2531PWINCCWPIPE MkWinChildcareCreateWorkerPipe(unsigned iWhich, unsigned int idxWorker)
2532{
2533 /*
2534 * We try generate a reasonably unique name from the get go, so this retry
2535 * loop shouldn't really ever be needed. But you never know.
2536 */
2537 static unsigned s_iSeqNo = 0;
2538 DWORD const cMaxInstances = 1;
2539 DWORD const cbPipe = 4096;
2540 DWORD const cMsTimeout = 0;
2541 unsigned cTries = 256;
2542 while (cTries-- > 0)
2543 {
2544 /* Create the pipe (our end). */
2545 HANDLE hPipeRead;
2546 DWORD fOpenMode = PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED | FILE_FLAG_FIRST_PIPE_INSTANCE;
2547 DWORD fPipeMode = PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT | PIPE_REJECT_REMOTE_CLIENTS;
2548 WCHAR wszName[MAX_PATH];
2549 s_iSeqNo++;
2550 _snwprintf(wszName, MAX_PATH, L"\\\\.\\pipe\\kmk-winchildren-%u-%u-%u-%s-%u-%u",
2551 GetCurrentProcessId(), GetCurrentThreadId(), idxWorker, iWhich == 1 ? L"out" : L"err", s_iSeqNo, GetTickCount());
2552 hPipeRead = CreateNamedPipeW(wszName, fOpenMode, fPipeMode, cMaxInstances, cbPipe, cbPipe, cMsTimeout, NULL /*pSecAttr*/);
2553 if (hPipeRead == INVALID_HANDLE_VALUE && GetLastError() == ERROR_INVALID_PARAMETER)
2554 {
2555 fOpenMode &= ~FILE_FLAG_FIRST_PIPE_INSTANCE;
2556 fPipeMode &= ~PIPE_REJECT_REMOTE_CLIENTS;
2557 hPipeRead = CreateNamedPipeW(wszName, fOpenMode, fPipeMode, cMaxInstances, cbPipe, cbPipe, cMsTimeout, NULL /*pSecAttr*/);
2558 }
2559 if (hPipeRead != INVALID_HANDLE_VALUE)
2560 {
2561 /* Connect the other end. */
2562 HANDLE hPipeWrite = CreateFileW(wszName, GENERIC_WRITE | FILE_READ_ATTRIBUTES, 0 /*fShareMode*/, NULL /*pSecAttr*/,
2563 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL /*hTemplateFile*/);
2564 if (hPipeWrite != INVALID_HANDLE_VALUE)
2565 {
2566 /*
2567 * Create the event object and we're done.
2568 *
2569 * It starts in signalled stated so we don't need special code
2570 * for handing when we start waiting.
2571 */
2572 HANDLE hEvent = CreateEventW(NULL /*pSecAttr*/, TRUE /*fManualReset*/, TRUE /*fInitialState*/, NULL /*pwszName*/);
2573 if (hEvent != NULL)
2574 {
2575 PWINCCWPIPE pPipe = (PWINCCWPIPE)xcalloc(sizeof(*pPipe));
2576 pPipe->hPipeMine = hPipeRead;
2577 pPipe->hPipeChild = hPipeWrite;
2578 pPipe->hEvent = hEvent;
2579 pPipe->iWhich = iWhich;
2580 pPipe->fReadPending = FALSE;
2581 pPipe->cbBuffer = cbPipe;
2582 pPipe->pbBuffer = xcalloc(cbPipe);
2583 return pPipe;
2584 }
2585
2586 CloseHandle(hPipeWrite);
2587 CloseHandle(hPipeRead);
2588 return NULL;
2589 }
2590 CloseHandle(hPipeRead);
2591 }
2592 }
2593 return NULL;
2594}
2595
2596/**
2597 * Destroys a childcare worker pipe.
2598 *
2599 * @param pPipe The pipe.
2600 */
2601void MkWinChildcareDeleteWorkerPipe(PWINCCWPIPE pPipe)
2602{
2603 if (pPipe->hPipeChild != NULL)
2604 {
2605 CloseHandle(pPipe->hPipeChild);
2606 pPipe->hPipeChild = NULL;
2607 }
2608
2609 if (pPipe->hPipeMine != NULL)
2610 {
2611 if (pPipe->fReadPending)
2612 if (!CancelIo(pPipe->hPipeMine))
2613 WaitForSingleObject(pPipe->hEvent, INFINITE);
2614 CloseHandle(pPipe->hPipeMine);
2615 pPipe->hPipeMine = NULL;
2616 }
2617
2618 if (pPipe->hEvent != NULL)
2619 {
2620 CloseHandle(pPipe->hEvent);
2621 pPipe->hEvent = NULL;
2622 }
2623
2624 if (pPipe->pbBuffer)
2625 {
2626 free(pPipe->pbBuffer);
2627 pPipe->pbBuffer = NULL;
2628 }
2629}
2630
2631/**
2632 * Initializes the processor group allocator.
2633 *
2634 * @param pState The allocator to initialize.
2635 */
2636void MkWinChildInitCpuGroupAllocator(PMKWINCHILDCPUGROUPALLOCSTATE pState)
2637{
2638 /* We shift the starting group with the make nesting level as part of
2639 our very simple distribution strategy. */
2640 pState->idxGroup = makelevel;
2641 pState->idxProcessorInGroup = 0;
2642}
2643
2644/**
2645 * Allocate CPU group for the next child process.
2646 *
2647 * @returns CPU group.
2648 * @param pState The allocator state. Must be initialized by
2649 * MkWinChildInitCpuGroupAllocator().
2650 */
2651unsigned int MkWinChildAllocateCpuGroup(PMKWINCHILDCPUGROUPALLOCSTATE pState)
2652{
2653 unsigned int iGroup = 0;
2654 if (g_cProcessorGroups > 1)
2655 {
2656 unsigned int cMaxInGroup;
2657 unsigned int cInGroup;
2658
2659 iGroup = pState->idxGroup % g_cProcessorGroups;
2660
2661 /* Advance. We employ a very simple strategy that does 50% in
2662 each group for each group cycle. Odd processor counts are
2663 caught in odd group cycles. The init function selects the
2664 starting group based on make nesting level to avoid stressing
2665 out the first group. */
2666 cInGroup = ++pState->idxProcessorInGroup;
2667 cMaxInGroup = g_pacProcessorsInGroup[iGroup];
2668 if ( !(cMaxInGroup & 1)
2669 || !((pState->idxGroup / g_cProcessorGroups) & 1))
2670 cMaxInGroup /= 2;
2671 else
2672 cMaxInGroup = cMaxInGroup / 2 + 1;
2673 if (cInGroup >= cMaxInGroup)
2674 {
2675 pState->idxProcessorInGroup = 0;
2676 pState->idxGroup++;
2677 }
2678 }
2679 return iGroup;
2680}
2681
2682/**
2683 * Creates another childcare worker.
2684 *
2685 * @returns The new worker, if we succeeded.
2686 */
2687static PWINCHILDCAREWORKER mkWinChildcareCreateWorker(void)
2688{
2689 PWINCHILDCAREWORKER pWorker = (PWINCHILDCAREWORKER)xcalloc(sizeof(*pWorker));
2690 pWorker->uMagic = WINCHILDCAREWORKER_MAGIC;
2691 pWorker->idxWorker = g_cChildCareworkers;
2692 pWorker->hEvtIdle = CreateEventW(NULL, FALSE /*fManualReset*/, FALSE /*fInitialState*/, NULL /*pwszName*/);
2693 if (pWorker->hEvtIdle)
2694 {
2695 pWorker->pStdOut = MkWinChildcareCreateWorkerPipe(1, pWorker->idxWorker);
2696 if (pWorker->pStdOut)
2697 {
2698 pWorker->pStdErr = MkWinChildcareCreateWorkerPipe(2, pWorker->idxWorker);
2699 if (pWorker->pStdErr)
2700 {
2701 /* Before we start the thread, assign it to a processor group. */
2702 pWorker->iProcessorGroup = MkWinChildAllocateCpuGroup(&g_ProcessorGroupAllocator);
2703
2704 /* Try start the thread. */
2705 pWorker->hThread = (HANDLE)_beginthreadex(NULL, 0 /*cbStack*/, mkWinChildcareWorkerThread, pWorker,
2706 0, &pWorker->tid);
2707 if (pWorker->hThread != NULL)
2708 {
2709 pWorker->idxWorker = g_cChildCareworkers++; /* paranoia */
2710 g_papChildCareworkers[pWorker->idxWorker] = pWorker;
2711 return pWorker;
2712 }
2713
2714 /* Bail out! */
2715 ONS (error, NILF, "_beginthreadex failed: %u (%s)\n", errno, strerror(errno));
2716 MkWinChildcareDeleteWorkerPipe(pWorker->pStdErr);
2717 }
2718 else
2719 ON (error, NILF, "Failed to create stderr pipe: %u\n", GetLastError());
2720 MkWinChildcareDeleteWorkerPipe(pWorker->pStdOut);
2721 }
2722 else
2723 ON (error, NILF, "Failed to create stdout pipe: %u\n", GetLastError());
2724 CloseHandle(pWorker->hEvtIdle);
2725 }
2726 else
2727 ON (error, NILF, "CreateEvent failed: %u\n", GetLastError());
2728 pWorker->uMagic = ~WINCHILDCAREWORKER_MAGIC;
2729 free(pWorker);
2730 return NULL;
2731}
2732
2733/**
2734 * Helper for copying argument and environment vectors.
2735 *
2736 * @returns Single alloc block copy.
2737 * @param papszSrc The source vector.
2738 * @param pcbStrings Where to return the size of the strings & terminator.
2739 */
2740static char **mkWinChildCopyStringArray(char **papszSrc, size_t *pcbStrings)
2741{
2742 const char *psz;
2743 char **papszDstArray;
2744 char *pszDstStr;
2745 size_t i;
2746
2747 /* Calc sizes first. */
2748 size_t cbStrings = 1; /* (one extra for terminator string) */
2749 size_t cStrings = 0;
2750 while ((psz = papszSrc[cStrings]) != NULL)
2751 {
2752 cbStrings += strlen(psz) + 1;
2753 cStrings++;
2754 }
2755 *pcbStrings = cbStrings;
2756
2757 /* Allocate destination. */
2758 papszDstArray = (char **)xmalloc(cbStrings + (cStrings + 1) * sizeof(papszDstArray[0]));
2759 pszDstStr = (char *)&papszDstArray[cStrings + 1];
2760
2761 /* Copy it. */
2762 for (i = 0; i < cStrings; i++)
2763 {
2764 const char *pszSource = papszSrc[i];
2765 size_t cchString = strlen(pszSource);
2766 papszDstArray[i] = pszDstStr;
2767 memcpy(pszDstStr, pszSource, cchString);
2768 pszDstStr += cchString;
2769 *pszDstStr++ = '\0';
2770 }
2771 *pszDstStr = '\0';
2772 assert(&pszDstStr[1] - papszDstArray[0] == cbStrings);
2773 papszDstArray[i] = NULL;
2774 return papszDstArray;
2775}
2776
2777/**
2778 * Allocate and init a WINCHILD.
2779 *
2780 * @returns The new windows child structure.
2781 * @param enmType The child type.
2782 */
2783static PWINCHILD mkWinChildNew(WINCHILDTYPE enmType)
2784{
2785 PWINCHILD pChild = xcalloc(sizeof(*pChild));
2786 pChild->enmType = enmType;
2787 pChild->fCoreDumped = 0;
2788 pChild->iSignal = 0;
2789 pChild->iExitCode = 222222;
2790 pChild->uMagic = WINCHILD_MAGIC;
2791 pChild->pid = (intptr_t)pChild;
2792 return pChild;
2793}
2794
2795/**
2796 * Destructor for WINCHILD.
2797 *
2798 * @param pChild The child structure to destroy.
2799 */
2800static void mkWinChildDelete(PWINCHILD pChild)
2801{
2802 assert(pChild->uMagic == WINCHILD_MAGIC);
2803 pChild->uMagic = ~WINCHILD_MAGIC;
2804
2805 switch (pChild->enmType)
2806 {
2807 case WINCHILDTYPE_PROCESS:
2808 {
2809 if (pChild->u.Process.papszArgs)
2810 {
2811 free(pChild->u.Process.papszArgs);
2812 pChild->u.Process.papszArgs = NULL;
2813 }
2814 if (pChild->u.Process.cbEnvStrings && pChild->u.Process.papszEnv)
2815 {
2816 free(pChild->u.Process.papszEnv);
2817 pChild->u.Process.papszEnv = NULL;
2818 }
2819 if (pChild->u.Process.pszShell)
2820 {
2821 free(pChild->u.Process.pszShell);
2822 pChild->u.Process.pszShell = NULL;
2823 }
2824 if (pChild->u.Process.hProcess)
2825 {
2826 CloseHandle(pChild->u.Process.hProcess);
2827 pChild->u.Process.hProcess = NULL;
2828 }
2829 mkWinChildcareWorkerCloseStandardHandles(pChild);
2830 break;
2831 }
2832
2833#ifdef KMK
2834 case WINCHILDTYPE_BUILT_IN:
2835 if (pChild->u.BuiltIn.papszArgs)
2836 {
2837 free(pChild->u.BuiltIn.papszArgs);
2838 pChild->u.BuiltIn.papszArgs = NULL;
2839 }
2840 if (pChild->u.BuiltIn.papszEnv)
2841 {
2842 free(pChild->u.BuiltIn.papszEnv);
2843 pChild->u.BuiltIn.papszEnv = NULL;
2844 }
2845 break;
2846
2847 case WINCHILDTYPE_APPEND:
2848 if (pChild->u.Append.pszFilename)
2849 {
2850 free(pChild->u.Append.pszFilename);
2851 pChild->u.Append.pszFilename = NULL;
2852 }
2853 if (pChild->u.Append.pszAppend)
2854 {
2855 free(pChild->u.Append.pszAppend);
2856 pChild->u.Append.pszAppend = NULL;
2857 }
2858 break;
2859
2860 case WINCHILDTYPE_SUBMIT:
2861 if (pChild->u.Submit.pvSubmitWorker)
2862 {
2863 kSubmitSubProcCleanup((intptr_t)pChild->u.Submit.pvSubmitWorker);
2864 pChild->u.Submit.pvSubmitWorker = NULL;
2865 }
2866 break;
2867
2868 case WINCHILDTYPE_REDIRECT:
2869 if (pChild->u.Redirect.hProcess)
2870 {
2871 CloseHandle(pChild->u.Redirect.hProcess);
2872 pChild->u.Redirect.hProcess = NULL;
2873 }
2874 break;
2875#endif /* KMK */
2876
2877 default:
2878 assert(0);
2879 }
2880
2881 free(pChild);
2882}
2883
2884/**
2885 * Queues the child with a worker, creating new workers if necessary.
2886 *
2887 * @returns 0 on success, windows error code on failure (child destroyed).
2888 * @param pChild The child.
2889 * @param pPid Where to return the PID (optional).
2890 */
2891static int mkWinChildPushToCareWorker(PWINCHILD pChild, pid_t *pPid)
2892{
2893 PWINCHILDCAREWORKER pWorker = NULL;
2894 PWINCHILD pOldChild;
2895 PWINCHILD pCurChild;
2896
2897 /*
2898 * There are usually idle workers around, except for at the start.
2899 */
2900 if (g_cIdleChildcareWorkers > 0)
2901 {
2902 /*
2903 * Try the idle hint first and move forward from it.
2904 */
2905 unsigned int const cWorkers = g_cChildCareworkers;
2906 unsigned int iHint = g_idxLastChildcareWorker;
2907 unsigned int i;
2908 for (i = iHint; i < cWorkers; i++)
2909 {
2910 PWINCHILDCAREWORKER pPossibleWorker = g_papChildCareworkers[i];
2911 if (pPossibleWorker->fIdle)
2912 {
2913 pWorker = pPossibleWorker;
2914 break;
2915 }
2916 }
2917 if (!pWorker)
2918 {
2919 /* Scan from the start. */
2920 if (iHint > cWorkers)
2921 iHint = cWorkers;
2922 for (i = 0; i < iHint; i++)
2923 {
2924 PWINCHILDCAREWORKER pPossibleWorker = g_papChildCareworkers[i];
2925 if (pPossibleWorker->fIdle)
2926 {
2927 pWorker = pPossibleWorker;
2928 break;
2929 }
2930 }
2931 }
2932 }
2933 if (!pWorker)
2934 {
2935 /*
2936 * Try create more workers if we haven't reached the max yet.
2937 */
2938 if (g_cChildCareworkers < g_cChildCareworkersMax)
2939 pWorker = mkWinChildcareCreateWorker();
2940
2941 /*
2942 * Queue it with an existing worker. Look for one without anthing extra scheduled.
2943 */
2944 if (!pWorker)
2945 {
2946 unsigned int i = g_cChildCareworkers;
2947 if (i == 0)
2948 fatal(NILF, 0, _("Failed to create worker threads for managing child processes!\n"));
2949 pWorker = g_papChildCareworkers[--i];
2950 if (pWorker->pTailTodoChildren)
2951 while (i-- > 0)
2952 {
2953 PWINCHILDCAREWORKER pPossibleWorker = g_papChildCareworkers[i];
2954 if (!pPossibleWorker->pTailTodoChildren)
2955 {
2956 pWorker = pPossibleWorker;
2957 break;
2958 }
2959 }
2960 }
2961 }
2962
2963 /*
2964 * Do the queueing.
2965 */
2966 pOldChild = NULL;
2967 for (;;)
2968 {
2969 pChild->pNext = pOldChild;
2970 pCurChild = _InterlockedCompareExchangePointer((void **)&pWorker->pTailTodoChildren, pChild, pOldChild);
2971 if (pCurChild == pOldChild)
2972 {
2973 DWORD volatile dwErr;
2974 _InterlockedIncrement(&g_cPendingChildren);
2975 if ( !pWorker->fIdle
2976 || SetEvent(pWorker->hEvtIdle))
2977 {
2978 *pPid = pChild->pid;
2979 return 0;
2980 }
2981
2982 _InterlockedDecrement(&g_cPendingChildren);
2983 dwErr = GetLastError();
2984 assert(0);
2985 mkWinChildDelete(pChild);
2986 return dwErr ? dwErr : -20;
2987 }
2988 pOldChild = pCurChild;
2989 }
2990}
2991
2992/**
2993 * Creates a regular child process (job.c).
2994 *
2995 * Will copy the information and push it to a childcare thread that does the
2996 * actual process creation.
2997 *
2998 * @returns 0 on success, windows status code on failure.
2999 * @param papszArgs The arguments.
3000 * @param papszEnv The environment (optional).
3001 * @param pszShell The SHELL variable value (optional).
3002 * @param pMkChild The make child structure (optional).
3003 * @param pPid Where to return the pid.
3004 */
3005int MkWinChildCreate(char **papszArgs, char **papszEnv, const char *pszShell, struct child *pMkChild, pid_t *pPid)
3006{
3007 PWINCHILD pChild = mkWinChildNew(WINCHILDTYPE_PROCESS);
3008 pChild->pMkChild = pMkChild;
3009
3010 pChild->u.Process.papszArgs = mkWinChildCopyStringArray(papszArgs, &pChild->u.Process.cbArgsStrings);
3011 if ( !papszEnv
3012 || !pMkChild
3013 || pMkChild->environment == papszEnv)
3014 {
3015 pChild->u.Process.cbEnvStrings = 0;
3016 pChild->u.Process.papszEnv = papszEnv;
3017 }
3018 else
3019 pChild->u.Process.papszEnv = mkWinChildCopyStringArray(papszEnv, &pChild->u.Process.cbEnvStrings);
3020 if (pszShell)
3021 pChild->u.Process.pszShell = xstrdup(pszShell);
3022 pChild->u.Process.hStdOut = INVALID_HANDLE_VALUE;
3023 pChild->u.Process.hStdErr = INVALID_HANDLE_VALUE;
3024
3025 /* We always catch the output in order to prevent character soups courtesy
3026 of the microsoft CRT and/or linkers writing character by character to the
3027 console. Always try write whole lines, even when --output-sync is none. */
3028 pChild->u.Process.fCatchOutput = TRUE;
3029
3030 return mkWinChildPushToCareWorker(pChild, pPid);
3031}
3032
3033/**
3034 * Creates a chile process with a pipe hooked up to stdout.
3035 *
3036 * @returns 0 on success, non-zero on failure.
3037 * @param papszArgs The argument vector.
3038 * @param papszEnv The environment vector (optional).
3039 * @param fdErr File descriptor to hook up to stderr.
3040 * @param pPid Where to return the pid.
3041 * @param pfdReadPipe Where to return the read end of the pipe.
3042 */
3043int MkWinChildCreateWithStdOutPipe(char **papszArgs, char **papszEnv, int fdErr, pid_t *pPid, int *pfdReadPipe)
3044{
3045 /*
3046 * Create the pipe.
3047 */
3048 HANDLE hReadPipe;
3049 HANDLE hWritePipe;
3050 if (CreatePipe(&hReadPipe, &hWritePipe, NULL, 0 /* default size */))
3051 {
3052 //if (SetHandleInformation(hWritePipe, HANDLE_FLAG_INHERIT /* clear */ , HANDLE_FLAG_INHERIT /*set*/))
3053 {
3054 int fdReadPipe = _open_osfhandle((intptr_t)hReadPipe, O_RDONLY);
3055 if (fdReadPipe >= 0)
3056 {
3057 PWINCHILD pChild;
3058 int rc;
3059
3060 /*
3061 * Get a handle for fdErr. Ignore failure.
3062 */
3063 HANDLE hStdErr = INVALID_HANDLE_VALUE;
3064 if (fdErr >= 0)
3065 {
3066 HANDLE hNative = (HANDLE)_get_osfhandle(fdErr);
3067 if (!DuplicateHandle(GetCurrentProcess(), hNative, GetCurrentProcess(),
3068 &hStdErr, 0 /*DesiredAccess*/, TRUE /*fInherit*/, DUPLICATE_SAME_ACCESS))
3069 {
3070 ONN(error, NILF, _("DuplicateHandle failed on stderr descriptor (%u): %u\n"), fdErr, GetLastError());
3071 hStdErr = INVALID_HANDLE_VALUE;
3072 }
3073 }
3074
3075 /*
3076 * Push it off to the worker thread.
3077 */
3078 pChild = mkWinChildNew(WINCHILDTYPE_PROCESS);
3079 pChild->u.Process.papszArgs = mkWinChildCopyStringArray(papszArgs, &pChild->u.Process.cbArgsStrings);
3080 pChild->u.Process.papszEnv = mkWinChildCopyStringArray(papszEnv ? papszEnv : environ,
3081 &pChild->u.Process.cbEnvStrings);
3082 //if (pszShell)
3083 // pChild->u.Process.pszShell = xstrdup(pszShell);
3084 pChild->u.Process.hStdOut = hWritePipe;
3085 pChild->u.Process.hStdErr = hStdErr;
3086 pChild->u.Process.fCloseStdErr = TRUE;
3087 pChild->u.Process.fCloseStdOut = TRUE;
3088
3089 rc = mkWinChildPushToCareWorker(pChild, pPid);
3090 if (rc == 0)
3091 *pfdReadPipe = fdReadPipe;
3092 else
3093 {
3094 ON(error, NILF, _("mkWinChildPushToCareWorker failed on pipe: %d\n"), rc);
3095 close(fdReadPipe);
3096 *pfdReadPipe = -1;
3097 *pPid = -1;
3098 }
3099 return rc;
3100 }
3101
3102 ON(error, NILF, _("_open_osfhandle failed on pipe: %u\n"), errno);
3103 }
3104 //else
3105 // ON(error, NILF, _("SetHandleInformation failed on pipe: %u\n"), GetLastError());
3106 if (hReadPipe != INVALID_HANDLE_VALUE)
3107 CloseHandle(hReadPipe);
3108 CloseHandle(hWritePipe);
3109 }
3110 else
3111 ON(error, NILF, _("CreatePipe failed: %u\n"), GetLastError());
3112 *pfdReadPipe = -1;
3113 *pPid = -1;
3114 return -1;
3115}
3116
3117#ifdef KMK
3118
3119/**
3120 * Interface used by kmkbuiltin.c for executing builtin commands on threads.
3121 *
3122 * @returns 0 on success, windows status code on failure.
3123 * @param pBuiltIn The kmk built-in command entry.
3124 * @param cArgs The number of arguments in papszArgs.
3125 * @param papszArgs The argument vector.
3126 * @param papszEnv The environment vector, optional.
3127 * @param pMkChild The make child structure.
3128 * @param pPid Where to return the pid.
3129 */
3130int MkWinChildCreateBuiltIn(PCKMKBUILTINENTRY pBuiltIn, int cArgs, char **papszArgs, char **papszEnv,
3131 struct child *pMkChild, pid_t *pPid)
3132{
3133 size_t cbIgnored;
3134 PWINCHILD pChild = mkWinChildNew(WINCHILDTYPE_BUILT_IN);
3135 pChild->pMkChild = pMkChild;
3136 pChild->u.BuiltIn.pBuiltIn = pBuiltIn;
3137 pChild->u.BuiltIn.cArgs = cArgs;
3138 pChild->u.BuiltIn.papszArgs = mkWinChildCopyStringArray(papszArgs, &cbIgnored);
3139 pChild->u.BuiltIn.papszEnv = papszEnv ? mkWinChildCopyStringArray(papszEnv, &cbIgnored) : NULL;
3140 return mkWinChildPushToCareWorker(pChild, pPid);
3141}
3142
3143/**
3144 * Interface used by append.c for do the slow file system part.
3145 *
3146 * This will append the given buffer to the specified file and free the buffer.
3147 *
3148 * @returns 0 on success, windows status code on failure.
3149 *
3150 * @param pszFilename The name of the file to append to.
3151 * @param ppszAppend What to append. The pointer pointed to is set to
3152 * NULL once we've taken ownership of the buffer and
3153 * promise to free it.
3154 * @param cbAppend How much to append.
3155 * @param fTruncate Whether to truncate the file before appending to it.
3156 * @param pMkChild The make child structure.
3157 * @param pPid Where to return the pid.
3158 */
3159int MkWinChildCreateAppend(const char *pszFilename, char **ppszAppend, size_t cbAppend, int fTruncate,
3160 struct child *pMkChild, pid_t *pPid)
3161{
3162 size_t cbFilename = strlen(pszFilename) + 1;
3163 PWINCHILD pChild = mkWinChildNew(WINCHILDTYPE_APPEND);
3164 pChild->pMkChild = pMkChild;
3165 pChild->u.Append.fTruncate = fTruncate;
3166 pChild->u.Append.pszAppend = *ppszAppend;
3167 pChild->u.Append.cbAppend = cbAppend;
3168 pChild->u.Append.pszFilename = (char *)memcpy(xmalloc(cbFilename), pszFilename, cbFilename);
3169 *ppszAppend = NULL;
3170 return mkWinChildPushToCareWorker(pChild, pPid);
3171}
3172
3173/**
3174 * Interface used by kSubmit.c for registering stuff to wait on.
3175 *
3176 * @returns 0 on success, windows status code on failure.
3177 * @param hEvent The event object handle to wait on.
3178 * @param pvSubmitWorker The argument to pass back to kSubmit to clean up.
3179 * @param pStdOut Standard output pipe for the worker. Optional.
3180 * @param pStdErr Standard error pipe for the worker. Optional.
3181 * @param pMkChild The make child structure.
3182 * @param pPid Where to return the pid.
3183 */
3184int MkWinChildCreateSubmit(intptr_t hEvent, void *pvSubmitWorker, PWINCCWPIPE pStdOut, PWINCCWPIPE pStdErr,
3185 struct child *pMkChild, pid_t *pPid)
3186{
3187 PWINCHILD pChild = mkWinChildNew(WINCHILDTYPE_SUBMIT);
3188 pChild->pMkChild = pMkChild;
3189 pChild->u.Submit.hEvent = (HANDLE)hEvent;
3190 pChild->u.Submit.pvSubmitWorker = pvSubmitWorker;
3191 pChild->u.Submit.pStdOut = pStdOut;
3192 pChild->u.Submit.pStdErr = pStdErr;
3193 return mkWinChildPushToCareWorker(pChild, pPid);
3194}
3195
3196/**
3197 * Interface used by redirect.c for registering stuff to wait on.
3198 *
3199 * @returns 0 on success, windows status code on failure.
3200 * @param hProcess The process object to wait on.
3201 * @param pPid Where to return the pid.
3202 */
3203int MkWinChildCreateRedirect(intptr_t hProcess, pid_t *pPid)
3204{
3205 PWINCHILD pChild = mkWinChildNew(WINCHILDTYPE_REDIRECT);
3206 pChild->u.Redirect.hProcess = (HANDLE)hProcess;
3207 return mkWinChildPushToCareWorker(pChild, pPid);
3208}
3209
3210
3211/**
3212 * New interface used by redirect.c for spawning and waitin on a child.
3213 *
3214 * This interface is only used when kmk_builtin_redirect is already running on
3215 * a worker thread.
3216 *
3217 * @returns exit status.
3218 * @param pvWorker The worker instance.
3219 * @param pszExecutable The executable image to run.
3220 * @param papszArgs Argument vector.
3221 * @param fQuotedArgv Whether the argument vector is already quoted and
3222 * just need some space to be turned into a command
3223 * line.
3224 * @param papszEnvVars Environment vector.
3225 * @param pszCwd The working directory of the child. Optional.
3226 * @param pafReplace Which standard handles to replace. Maybe modified!
3227 * @param pahReplace The replacement handles. Maybe modified!
3228 *
3229 */
3230int MkWinChildBuiltInExecChild(void *pvWorker, const char *pszExecutable, char **papszArgs, BOOL fQuotedArgv,
3231 char **papszEnvVars, const char *pszCwd, BOOL pafReplace[3], HANDLE pahReplace[3])
3232{
3233 PWINCHILDCAREWORKER pWorker = (PWINCHILDCAREWORKER)pvWorker;
3234 WCHAR *pwszSearchPath = NULL;
3235 WCHAR *pwszzEnvironment = NULL;
3236 WCHAR *pwszCommandLine = NULL;
3237 WCHAR *pwszImageName = NULL;
3238 WCHAR *pwszCwd = NULL;
3239 BOOL fNeedShell = FALSE;
3240 PWINCHILD pChild;
3241 int rc;
3242 assert(pWorker->uMagic == WINCHILDCAREWORKER_MAGIC);
3243 pChild = pWorker->pCurChild;
3244 assert(pChild != NULL && pChild->uMagic == WINCHILD_MAGIC);
3245
3246 /*
3247 * Convert the CWD first since it's optional and we don't need to clean
3248 * up anything here if it fails.
3249 */
3250 if (pszCwd)
3251 {
3252 size_t cchCwd = strlen(pszCwd);
3253 int cwcCwd = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszCwd, cchCwd + 1, NULL, 0);
3254 pwszCwd = xmalloc((cwcCwd + 1) * sizeof(WCHAR)); /* (+1 in case cwcCwd is 0) */
3255 cwcCwd = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszCwd, cchCwd + 1, pwszCwd, cwcCwd + 1);
3256 if (!cwcCwd)
3257 {
3258 rc = GetLastError();
3259 MkWinChildError(pWorker, 1, _("MultiByteToWideChar failed to convert CWD (%s): %u\n"), pszCwd, (unsigned)rc);
3260 return rc;
3261 }
3262 }
3263
3264 /*
3265 * Before we search for the image, we convert the environment so we don't
3266 * have to traverse it twice to find the PATH.
3267 */
3268 rc = mkWinChildcareWorkerConvertEnvironment(pWorker, papszEnvVars ? papszEnvVars : environ, 0/*cbEnvStrings*/,
3269 &pwszzEnvironment, &pwszSearchPath);
3270 /*
3271 * Find the executable and maybe checking if it's a shell script, then
3272 * convert it to a command line.
3273 */
3274 if (rc == 0)
3275 rc = mkWinChildcareWorkerFindImage(pWorker, pszExecutable, pwszSearchPath, pwszzEnvironment, NULL /*pszShell*/,
3276 &pwszImageName, &fNeedShell, &pChild->fProbableClExe);
3277 if (rc == 0)
3278 {
3279 assert(!fNeedShell);
3280 if (!fQuotedArgv)
3281 rc = mkWinChildcareWorkerConvertCommandline(pWorker, papszArgs, 0 /*fFlags*/, &pwszCommandLine);
3282 else
3283 rc = mkWinChildcareWorkerConvertQuotedArgvToCommandline(pWorker, papszArgs, &pwszCommandLine);
3284
3285 /*
3286 * Create the child process.
3287 */
3288 if (rc == 0)
3289 {
3290 HANDLE hProcess;
3291 rc = mkWinChildcareWorkerCreateProcess(pWorker, pwszImageName, pwszCommandLine, pwszzEnvironment,
3292 pwszCwd, pafReplace, pahReplace, TRUE /*fCatchOutput*/, &hProcess);
3293 if (rc == 0)
3294 {
3295 /*
3296 * Wait for the child to complete.
3297 */
3298 rc = mkWinChildcareWorkerWaitForProcess(pWorker, pChild, hProcess, pwszImageName, TRUE /*fCatchOutput*/);
3299 CloseHandle(hProcess);
3300 }
3301 }
3302 }
3303
3304 free(pwszCwd);
3305 free(pwszCommandLine);
3306 free(pwszImageName);
3307 free(pwszzEnvironment);
3308
3309 return rc;
3310}
3311
3312#endif /* CONFIG_NEW_WIN_CHILDREN */
3313
3314/**
3315 * Interface used to kill process when processing Ctrl-C and fatal errors.
3316 *
3317 * @returns 0 on success, -1 & errno on error.
3318 * @param pid The process to kill (PWINCHILD).
3319 * @param iSignal What to kill it with.
3320 * @param pMkChild The make child structure for validation.
3321 */
3322int MkWinChildKill(pid_t pid, int iSignal, struct child *pMkChild)
3323{
3324 PWINCHILD pChild = (PWINCHILD)pid;
3325 if (pChild)
3326 {
3327 assert(pChild->uMagic == WINCHILD_MAGIC);
3328 if (pChild->uMagic == WINCHILD_MAGIC)
3329 {
3330 switch (pChild->enmType)
3331 {
3332 case WINCHILDTYPE_PROCESS:
3333 assert(pChild->pMkChild == pMkChild);
3334 TerminateProcess(pChild->u.Process.hProcess, DBG_TERMINATE_PROCESS);
3335 pChild->iSignal = iSignal;
3336 break;
3337#ifdef KMK
3338 case WINCHILDTYPE_SUBMIT:
3339 {
3340 pChild->iSignal = iSignal;
3341 SetEvent(pChild->u.Submit.hEvent);
3342 break;
3343 }
3344
3345 case WINCHILDTYPE_REDIRECT:
3346 TerminateProcess(pChild->u.Redirect.hProcess, DBG_TERMINATE_PROCESS);
3347 pChild->iSignal = iSignal;
3348 break;
3349
3350 case WINCHILDTYPE_BUILT_IN:
3351 break;
3352
3353#endif /* KMK */
3354 default:
3355 assert(0);
3356 }
3357 }
3358 }
3359 return -1;
3360}
3361
3362/**
3363 * Wait for a child process to complete
3364 *
3365 * @returns 0 on success, windows error code on failure.
3366 * @param fBlock Whether to block.
3367 * @param pPid Where to return the pid if a child process
3368 * completed. This is set to zero if none.
3369 * @param piExitCode Where to return the exit code.
3370 * @param piSignal Where to return the exit signal number.
3371 * @param pfCoreDumped Where to return the core dumped indicator.
3372 * @param ppMkChild Where to return the associated struct child pointer.
3373 */
3374int MkWinChildWait(int fBlock, pid_t *pPid, int *piExitCode, int *piSignal, int *pfCoreDumped, struct child **ppMkChild)
3375{
3376 PWINCHILD pChild;
3377
3378 *pPid = 0;
3379 *piExitCode = -222222;
3380 *pfCoreDumped = 0;
3381 *ppMkChild = NULL;
3382
3383 /*
3384 * Wait if necessary.
3385 */
3386 if (fBlock && !g_pTailCompletedChildren && g_cPendingChildren > 0)
3387 {
3388 DWORD dwStatus = WaitForSingleObject(g_hEvtWaitChildren, INFINITE);
3389 if (dwStatus == WAIT_FAILED)
3390 return (int)GetLastError();
3391 }
3392
3393 /*
3394 * Try unlink the last child in the LIFO.
3395 */
3396 pChild = g_pTailCompletedChildren;
3397 if (!pChild)
3398 return 0;
3399 pChild = mkWinChildDequeFromLifo(&g_pTailCompletedChildren, pChild);
3400 assert(pChild);
3401
3402 /*
3403 * Set return values and ditch the child structure.
3404 */
3405 *pPid = pChild->pid;
3406 *piExitCode = pChild->iExitCode;
3407 *pfCoreDumped = pChild->fCoreDumped;
3408 *ppMkChild = pChild->pMkChild;
3409 switch (pChild->enmType)
3410 {
3411 case WINCHILDTYPE_PROCESS:
3412 break;
3413#ifdef KMK
3414 case WINCHILDTYPE_BUILT_IN:
3415 case WINCHILDTYPE_APPEND:
3416 case WINCHILDTYPE_SUBMIT:
3417 case WINCHILDTYPE_REDIRECT:
3418 break;
3419#endif /* KMK */
3420 default:
3421 assert(0);
3422 }
3423 mkWinChildDelete(pChild);
3424
3425#ifdef KMK
3426 /* Flush the volatile directory cache. */
3427 dir_cache_invalid_after_job();
3428#endif
3429 return 0;
3430}
3431
3432/**
3433 * Get the child completed event handle.
3434 *
3435 * Needed when w32os.c is waiting for a job token to become available, given
3436 * that completed children is the typical source of these tokens (esp. for kmk).
3437 *
3438 * @returns Zero if no active children, event handle if waiting is required.
3439 */
3440intptr_t MkWinChildGetCompleteEventHandle(void)
3441{
3442 /* We don't return the handle if we've got completed children. This
3443 is a safe guard against being called twice in a row without any
3444 MkWinChildWait call inbetween. */
3445 if (!g_pTailCompletedChildren)
3446 return (intptr_t)g_hEvtWaitChildren;
3447 return 0;
3448}
3449
3450/**
3451 * Emulate execv() for restarting kmk after one ore more makefiles has been
3452 * made.
3453 *
3454 * Does not return.
3455 *
3456 * @param papszArgs The arguments.
3457 * @param papszEnv The environment.
3458 */
3459void MkWinChildReExecMake(char **papszArgs, char **papszEnv)
3460{
3461 PROCESS_INFORMATION ProcInfo;
3462 STARTUPINFOW StartupInfo;
3463 WCHAR *pwszCommandLine;
3464 WCHAR *pwszzEnvironment;
3465 WCHAR *pwszPathIgnored;
3466 int rc;
3467
3468 /*
3469 * Get the executable name.
3470 */
3471 WCHAR wszImageName[MKWINCHILD_MAX_PATH];
3472 DWORD cwcImageName = GetModuleFileNameW(GetModuleHandle(NULL), wszImageName, MKWINCHILD_MAX_PATH);
3473 if (cwcImageName == 0)
3474 ON(fatal, NILF, _("MkWinChildReExecMake: GetModuleFileName failed: %u\n"), GetLastError());
3475
3476 /*
3477 * Create the command line and environment.
3478 */
3479 rc = mkWinChildcareWorkerConvertCommandline(NULL, papszArgs, 0 /*fFlags*/, &pwszCommandLine);
3480 if (rc != 0)
3481 ON(fatal, NILF, _("MkWinChildReExecMake: mkWinChildcareWorkerConvertCommandline failed: %u\n"), rc);
3482
3483 rc = mkWinChildcareWorkerConvertEnvironment(NULL, papszEnv ? papszEnv : environ, 0 /*cbEnvStrings*/,
3484 &pwszzEnvironment, &pwszPathIgnored);
3485 if (rc != 0)
3486 ON(fatal, NILF, _("MkWinChildReExecMake: mkWinChildcareWorkerConvertEnvironment failed: %u\n"), rc);
3487
3488
3489 /*
3490 * Fill out the startup info and try create the process.
3491 */
3492 memset(&ProcInfo, 0, sizeof(ProcInfo));
3493 memset(&StartupInfo, 0, sizeof(StartupInfo));
3494 StartupInfo.cb = sizeof(StartupInfo);
3495 GetStartupInfoW(&StartupInfo);
3496 if (!CreateProcessW(wszImageName, pwszCommandLine, NULL /*pProcSecAttr*/, NULL /*pThreadSecAttr*/,
3497 TRUE /*fInheritHandles*/, CREATE_UNICODE_ENVIRONMENT, pwszzEnvironment, NULL /*pwsz*/,
3498 &StartupInfo, &ProcInfo))
3499 ON(fatal, NILF, _("MkWinChildReExecMake: CreateProcessW failed: %u\n"), GetLastError());
3500 CloseHandle(ProcInfo.hThread);
3501
3502 /*
3503 * Wait for it to complete and forward the status code to our parent.
3504 */
3505 for (;;)
3506 {
3507 DWORD dwExitCode = -2222;
3508 DWORD dwStatus = WaitForSingleObject(ProcInfo.hProcess, INFINITE);
3509 if ( dwStatus == WAIT_IO_COMPLETION
3510 || dwStatus == WAIT_TIMEOUT /* whatever */)
3511 continue; /* however unlikely, these aren't fatal. */
3512
3513 /* Get the status code and terminate. */
3514 if (dwStatus == WAIT_OBJECT_0)
3515 {
3516 if (!GetExitCodeProcess(ProcInfo.hProcess, &dwExitCode))
3517 {
3518 ON(fatal, NILF, _("MkWinChildReExecMake: GetExitCodeProcess failed: %u\n"), GetLastError());
3519 dwExitCode = -2222;
3520 }
3521 }
3522 else if (dwStatus)
3523 dwExitCode = dwStatus;
3524
3525 CloseHandle(ProcInfo.hProcess);
3526 for (;;)
3527 exit(dwExitCode);
3528 }
3529}
3530
3531#ifdef WITH_RW_LOCK
3532/** Serialization with kmkbuiltin_redirect. */
3533void MkWinChildExclusiveAcquire(void)
3534{
3535 AcquireSRWLockExclusive(&g_RWLock);
3536}
3537
3538/** Serialization with kmkbuiltin_redirect. */
3539void MkWinChildExclusiveRelease(void)
3540{
3541 ReleaseSRWLockExclusive(&g_RWLock);
3542}
3543#endif /* WITH_RW_LOCK */
3544
3545/**
3546 * Implementation of the CLOSE_ON_EXEC macro.
3547 *
3548 * @returns errno value.
3549 * @param fd The file descriptor to hide from children.
3550 */
3551int MkWinChildUnrelatedCloseOnExec(int fd)
3552{
3553 if (fd >= 0)
3554 {
3555 HANDLE hNative = (HANDLE)_get_osfhandle(fd);
3556 if (hNative != INVALID_HANDLE_VALUE && hNative != NULL)
3557 {
3558 if (SetHandleInformation(hNative, HANDLE_FLAG_INHERIT /*clear*/ , 0 /*set*/))
3559 return 0;
3560 }
3561 return errno;
3562 }
3563 return EINVAL;
3564}
3565
Note: See TracBrowser for help on using the repository browser.

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