VirtualBox

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

Last change on this file since 3617 was 3617, checked in by bird, 6 weeks ago

kmk/winchildren.c: Use GetLogicalProcessorInformationEx/RelationGroup to get the group affinity masks and use these in calls to SetThreadGroupAffinity when we have them.

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

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