VirtualBox

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

Last change on this file since 3353 was 3353, checked in by bird, 4 years ago

kmk/winchildren: Use windows job objects to terminate all orphaned children when the topmost kmk process terminates. There are a couple of command line options to disable/tweak this behaviour.

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

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