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

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

kmk/winchildren.c: Fixed argv -> cmdline conversion bug.

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