VirtualBox

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

Last change on this file since 3633 was 3627, checked in by bird, 5 months ago

kmk/winchildren.c: Skip the g_pfnSetThreadGroupAffinity stuff on windows 11 and later, just reset the g_pfnSetProcessDefaultCpuSetMasks and g_pfnSetThreadSelectedCpuSetMasks upon startup so the system is free to schedule things in whichever groups it thinks makes sense.

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

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