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

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

winchildren: Workaround for out of sync dir cache (temporary, right).

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 127.9 KB
Line 
1/* $Id: winchildren.c 3203 2018-03-28 22:23:23Z 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;
1441 else
1442 cwcDstExtra += 1 + cBackslashes;
1443 break;
1444
1445 case ' ':
1446 case '\t':
1447 if (!paArgInfo[i].fQuoteIt)
1448 {
1449 paArgInfo[i].fQuoteIt = 1;
1450 cwcDstExtra += 2;
1451 }
1452 cBackslashes = 0;
1453 break;
1454 }
1455 }
1456
1457 if ( cBackslashes > 0
1458 && paArgInfo[i].fQuoteIt
1459 && !(fFlags & (MKWCCWCMD_F_CYGWIN_SHELL | MKWCCWCMD_F_MKS_SHELL)))
1460 {
1461 cwcDstExtra += cBackslashes;
1462 paArgInfo[i].fEndSlashes = 1;
1463 }
1464
1465 paArgInfo[i].cwcDst = cwcDst + cwcDstExtra;
1466 paArgInfo[i].cwcDstExtra = cwcDstExtra;
1467 }
1468 }
1469
1470 if ( (fFlags & MKWCCWCMD_F_HAVE_KASH_C)
1471 && paArgInfo[i].fQuoteIt)
1472 {
1473 paArgInfo[i].fExtraSpace = 1;
1474 paArgInfo[i].cwcDst++;
1475 paArgInfo[i].cwcDstExtra++;
1476 }
1477
1478 cwcNeeded += (i != 0) + paArgInfo[i].cwcDst;
1479 }
1480
1481 /*
1482 * Allocate the result buffer and do the actual conversion.
1483 */
1484 pwszDst = pwszCmdLine = (WCHAR *)xmalloc(sizeof(WCHAR) * cwcNeeded);
1485 for (i = 0; i < cArgs; i++)
1486 {
1487 char *pszSrc = papszArgs[i];
1488 size_t cwcDst = paArgInfo[i].cwcDst;
1489
1490 if (i != 0)
1491 *pwszDst++ = L' ';
1492
1493 if (paArgInfo[i].fQuoteIt)
1494 {
1495 *pwszDst++ = L'"';
1496 cwcDst -= 2;
1497 }
1498
1499 if (!paArgInfo[i].fSlowly)
1500 {
1501 int cwcDst2 = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszSrc, paArgInfo[i].cchSrc, pwszDst, cwcDst + 1);
1502 assert(cwcDst2 >= 0);
1503 pwszDst += cwcDst;
1504 }
1505 else
1506 {
1507 /* Do the conversion into the end of the output buffer, then move
1508 it up to where it should be char by char. */
1509 size_t cBackslashes;
1510 size_t cwcLeft = paArgInfo[i].cwcDst - paArgInfo[i].cwcDstExtra;
1511 WCHAR volatile *pwchSlowSrc = pwszDst + paArgInfo[i].cwcDstExtra;
1512 WCHAR volatile *pwchSlowDst = pwszDst;
1513 int cwcDst2 = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszSrc, paArgInfo[i].cchSrc,
1514 (WCHAR *)pwchSlowSrc, cwcLeft + 1);
1515 assert(cwcDst2 >= 0);
1516
1517 cBackslashes = 0;
1518 while (cwcLeft-- > 0)
1519 {
1520 WCHAR wcSrc = *pwchSlowSrc++;
1521 if (wcSrc != L'\\' && wcSrc != L'"')
1522 cBackslashes = 0;
1523 else if (wcSrc == L'\\')
1524 cBackslashes++;
1525 else if ( (fFlags & (MKWCCWCMD_F_CYGWIN_SHELL | MKWCCWCMD_F_HAVE_SH))
1526 == (MKWCCWCMD_F_CYGWIN_SHELL | MKWCCWCMD_F_HAVE_SH))
1527 *pwchSlowDst++ = L'"'; /* cygwin: '"' instead of '\\', no escaped slashes. */
1528 else
1529 {
1530 if (!(fFlags & (MKWCCWCMD_F_CYGWIN_SHELL | MKWCCWCMD_F_MKS_SHELL)))
1531 cBackslashes = 1;
1532 while (cBackslashes-- > 0)
1533 *pwchSlowDst++ = L'\\';
1534 }
1535 *pwchSlowDst++ = wcSrc;
1536 }
1537
1538 if (paArgInfo[i].fEndSlashes)
1539 while (cBackslashes-- > 0)
1540 *pwchSlowDst++ = L'\\';
1541
1542 pwszDst += cwcDst;
1543 assert(pwszDst == (WCHAR *)pwchSlowDst);
1544 }
1545
1546 if (paArgInfo[i].fExtraSpace)
1547 *pwszDst++ = L' ';
1548 if (paArgInfo[i].fQuoteIt)
1549 *pwszDst++ = L'"';
1550 }
1551 *pwszDst = L'\0';
1552 *ppwszCommandLine = pwszCmdLine;
1553 return 0;
1554}
1555
1556static int mkWinChildcareWorkerConvertCommandlineWithShell(PWINCHILDCAREWORKER pWorker, const WCHAR *pwszShell, char **papszArgs,
1557 WCHAR **ppwszCommandLine)
1558{
1559 MkWinChildError(pWorker, 1, "%s: not found!\n", papszArgs[0]);
1560//__debugbreak();
1561 return ERROR_FILE_NOT_FOUND;
1562}
1563
1564/**
1565 * Searches the environment block for the PATH variable.
1566 *
1567 * @returns Pointer to the path in the block or "." in pwszPathFallback.
1568 * @param pwszzEnv The UTF-16 environment block to search.
1569 * @param pwszPathFallback Fallback.
1570 */
1571static const WCHAR *mkWinChildcareWorkerFindPathValue(const WCHAR *pwszzEnv, WCHAR pwszPathFallback[4])
1572{
1573 while (*pwszzEnv)
1574 {
1575 size_t cwcVar = wcslen(pwszzEnv);
1576 if (!IS_PATH_ENV_VAR(cwcVar, pwszzEnv))
1577 pwszzEnv += cwcVar + 1;
1578 else if (cwcVar > 5)
1579 return &pwszzEnv[5];
1580 else
1581 break;
1582 }
1583 pwszPathFallback[0] = L'.';
1584 pwszPathFallback[1] = L'\0';
1585 return pwszPathFallback;
1586}
1587
1588/**
1589 * Checks if we need to had this executable file to the shell.
1590 *
1591 * @returns TRUE if it's shell fooder, FALSE if we think windows can handle it.
1592 * @param hFile Handle to the file in question
1593 */
1594static BOOL mkWinChildcareWorkerCheckIfNeedShell(HANDLE hFile)
1595{
1596 /*
1597 * Read the first 512 bytes and check for an executable image header.
1598 */
1599 union
1600 {
1601 DWORD dwSignature;
1602 WORD wSignature;
1603 BYTE ab[128];
1604 } uBuf;
1605 DWORD cbRead;
1606 uBuf.dwSignature = 0;
1607 if ( ReadFile(hFile, &uBuf, sizeof(uBuf), &cbRead, NULL /*pOverlapped*/)
1608 && cbRead == sizeof(uBuf))
1609 {
1610 if (uBuf.wSignature == IMAGE_DOS_SIGNATURE)
1611 return FALSE;
1612 if (uBuf.dwSignature == IMAGE_NT_SIGNATURE)
1613 return FALSE;
1614 if ( uBuf.wSignature == IMAGE_OS2_SIGNATURE /* NE */
1615 || uBuf.wSignature == 0x5d4c /* LX */
1616 || uBuf.wSignature == IMAGE_OS2_SIGNATURE_LE /* LE */)
1617 return FALSE;
1618 }
1619 return TRUE;
1620}
1621
1622/**
1623 * Checks if the image path looks like microsoft CL.exe.
1624 *
1625 * @returns TRUE / FALSE.
1626 * @param pwszImagePath The executable image path to evalutate.
1627 * @param cwcImagePath The length of the image path.
1628 */
1629static BOOL mkWinChildIsProbableClExe(WCHAR const *pwszImagePath, size_t cwcImagePath)
1630{
1631 assert(pwszImagePath[cwcImagePath] == '\0');
1632 return cwcImagePath > 7
1633 && (pwszImagePath[cwcImagePath - 7] == L'/' || pwszImagePath[cwcImagePath - 7] == L'\\')
1634 && (pwszImagePath[cwcImagePath - 6] == L'c' || pwszImagePath[cwcImagePath - 6] == L'C')
1635 && (pwszImagePath[cwcImagePath - 5] == L'l' || pwszImagePath[cwcImagePath - 5] == L'L')
1636 && pwszImagePath[cwcImagePath - 4] == L'.'
1637 && (pwszImagePath[cwcImagePath - 3] == L'e' || pwszImagePath[cwcImagePath - 3] == L'E')
1638 && (pwszImagePath[cwcImagePath - 2] == L'x' || pwszImagePath[cwcImagePath - 2] == L'X')
1639 && (pwszImagePath[cwcImagePath - 1] == L'e' || pwszImagePath[cwcImagePath - 1] == L'E');
1640}
1641
1642/**
1643 * Temporary workaround for seemingly buggy kFsCache.c / dir-nt-bird.c.
1644 *
1645 * Something is not invalidated / updated correctly!
1646 */
1647static BOOL mkWinChildcareWorkerIsRegularFileW(PWINCHILDCAREWORKER pWorker, wchar_t const *pwszPath)
1648{
1649 BOOL fRet = FALSE;
1650#ifdef KMK
1651 if (utf16_regular_file_p(pwszPath))
1652 fRet = TRUE;
1653 else
1654#endif
1655 {
1656 /* Don't believe the cache. */
1657 DWORD dwAttr = GetFileAttributesW(pwszPath);
1658 if (dwAttr != INVALID_FILE_ATTRIBUTES)
1659 {
1660 if (!(dwAttr & FILE_ATTRIBUTE_DIRECTORY))
1661 {
1662#ifdef KMK
1663 extern void dir_cache_invalid_volatile(void);
1664 dir_cache_invalid_volatile();
1665 if (utf16_regular_file_p(pwszPath))
1666 MkWinChildError(pWorker, 1, "kFsCache was out of sync! pwszPath=%S\n", pwszPath);
1667 else
1668 {
1669 dir_cache_invalid_all();
1670 if (utf16_regular_file_p(pwszPath))
1671 MkWinChildError(pWorker, 1, "kFsCache was really out of sync! pwszPath=%S\n", pwszPath);
1672 else
1673 MkWinChildError(pWorker, 1, "kFsCache is really out of sync!! pwszPath=%S\n", pwszPath);
1674 }
1675#endif
1676 fRet = TRUE;
1677 }
1678 }
1679 }
1680 return fRet;
1681}
1682
1683
1684/**
1685 * Tries to locate the image file, searching the path and maybe falling back on
1686 * the shell in case it knows more (think cygwin with its own view of the file
1687 * system).
1688 *
1689 * This will also check for shell script, falling back on the shell too to
1690 * handle those.
1691 *
1692 * @returns 0 on success, windows error code on failure.
1693 * @param pWorker The childcare worker.
1694 * @param pszArg0 The first argument.
1695 * @param pwszSearchPath In case mkWinChildcareWorkerConvertEnvironment
1696 * had a chance of locating the search path already.
1697 * @param pwszzEnv The environment block, in case we need to look for
1698 * the path.
1699 * @param pszShell The shell.
1700 * @param ppwszImagePath Where to return the pointer to the image path. This
1701 * could be the shell.
1702 * @param pfNeedShell Where to return shell vs direct execution indicator.
1703 * @param pfProbableClExe Where to return an indicator of probably CL.EXE.
1704 */
1705static int mkWinChildcareWorkerFindImage(PWINCHILDCAREWORKER pWorker, char const *pszArg0, WCHAR *pwszSearchPath,
1706 WCHAR const *pwszzEnv, const char *pszShell,
1707 WCHAR **ppwszImagePath, BOOL *pfNeedShell, BOOL *pfProbableClExe)
1708{
1709 /** @todo Slap a cache on this code. We usually end up executing the same
1710 * stuff over and over again (e.g. compilers, linkers, etc).
1711 * Hitting the file system is slow on windows. */
1712
1713 /*
1714 * Convert pszArg0 to unicode so we can work directly on that.
1715 */
1716 WCHAR wszArg0[MKWINCHILD_MAX_PATH + 4]; /* +4 for painless '.exe' appending */
1717 DWORD dwErr;
1718 size_t cbArg0 = strlen(pszArg0) + 1;
1719 int const cwcArg0 = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszArg0, cbArg0, wszArg0, MKWINCHILD_MAX_PATH);
1720 if (cwcArg0 > 0)
1721 {
1722 HANDLE hFile = INVALID_HANDLE_VALUE;
1723 WCHAR wszPathBuf[MKWINCHILD_MAX_PATH + 4]; /* +4 for painless '.exe' appending */
1724 int cwc;
1725
1726 /*
1727 * If there isn't an .exe suffix, we may have to add one.
1728 * Also we ASSUME that .exe suffixes means no hash bang detection needed.
1729 */
1730 int const fHasExeSuffix = cwcArg0 > CSTRLEN(".exe")
1731 && wszArg0[cwcArg0 - 4] == '.'
1732 && (wszArg0[cwcArg0 - 3] == L'e' || wszArg0[cwcArg0 - 3] == L'E')
1733 && (wszArg0[cwcArg0 - 2] == L'x' || wszArg0[cwcArg0 - 2] == L'X')
1734 && (wszArg0[cwcArg0 - 1] == L'e' || wszArg0[cwcArg0 - 1] == L'E');
1735
1736 /*
1737 * If there isn't any path specified, we need to search the PATH env.var.
1738 */
1739 int const fHasPath = wszArg0[1] == L':'
1740 || wszArg0[0] == L'\\'
1741 || wszArg0[0] == L'/'
1742 || wmemchr(wszArg0, L'/', cwcArg0)
1743 || wmemchr(wszArg0, L'\\', cwcArg0);
1744
1745 /* Before we do anything, flip UNIX slashes to DOS ones. */
1746 WCHAR *pwc = wszArg0;
1747 while ((pwc = wcschr(pwc, L'/')) != NULL)
1748 *pwc++ = L'\\';
1749
1750 /* Don't need to set these all the time... */
1751 *pfNeedShell = FALSE;
1752 *pfProbableClExe = FALSE;
1753
1754 /*
1755 * If any kind of path is specified in arg0, we will not search the
1756 * PATH env.var and can limit ourselves to maybe slapping a .exe on to it.
1757 */
1758 if (fHasPath)
1759 {
1760 /*
1761 * If relative to a CWD, turn it into an absolute one.
1762 */
1763 unsigned cwcPath = cwcArg0;
1764 WCHAR *pwszPath = wszArg0;
1765 if ( *pwszPath != L'\\'
1766 && (pwszPath[1] != ':' || pwszPath[2] != L'\\') )
1767 {
1768 DWORD cwcAbsPath = GetFullPathNameW(wszArg0, MKWINCHILD_MAX_PATH, wszPathBuf, NULL);
1769 if (cwcAbsPath > 0)
1770 {
1771 cwcPath = cwcAbsPath + 1; /* include terminator, like MultiByteToWideChar does. */
1772 pwszPath = wszPathBuf;
1773 }
1774 }
1775
1776 /*
1777 * Check with .exe suffix first.
1778 * We don't open .exe files and look for hash bang stuff, we just
1779 * assume they are executable images that CreateProcess can deal with.
1780 */
1781 if (!fHasExeSuffix)
1782 {
1783 pwszPath[cwcPath - 1] = L'.';
1784 pwszPath[cwcPath ] = L'e';
1785 pwszPath[cwcPath + 1] = L'x';
1786 pwszPath[cwcPath + 2] = L'e';
1787 pwszPath[cwcPath + 3] = L'\0';
1788 }
1789
1790 if (mkWinChildcareWorkerIsRegularFileW(pWorker, pwszPath))
1791 {
1792 *pfProbableClExe = mkWinChildIsProbableClExe(pwszPath, cwcPath + 4 - 1);
1793 return mkWinChildDuplicateUtf16String(pwszPath, cwcPath + 4, ppwszImagePath);
1794 }
1795
1796 /*
1797 * If no suffix was specified, try without .exe too, but now we need
1798 * to see if it's for the shell or CreateProcess.
1799 */
1800 if (!fHasExeSuffix)
1801 {
1802 pwszPath[cwcPath - 1] = L'\0';
1803#ifdef KMK
1804 if (mkWinChildcareWorkerIsRegularFileW(pWorker, pwszPath))
1805#endif
1806 {
1807 hFile = CreateFileW(pwszPath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
1808 NULL /*pSecAttr*/, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1809 if (hFile != INVALID_HANDLE_VALUE)
1810 {
1811 *pfNeedShell = mkWinChildcareWorkerCheckIfNeedShell(hFile);
1812 CloseHandle(hFile);
1813 if (!*pfNeedShell)
1814 {
1815 *pfProbableClExe = mkWinChildIsProbableClExe(pwszPath, cwcPath - 1);
1816 return mkWinChildDuplicateUtf16String(pwszPath, cwcPath, ppwszImagePath);
1817 }
1818 }
1819 }
1820 }
1821 }
1822 /*
1823 * No path, need to search the PATH env.var. for the executable, maybe
1824 * adding an .exe suffix while do so if that is missing.
1825 */
1826 else
1827 {
1828 BOOL fSearchedCwd = FALSE;
1829 WCHAR wszPathFallback[4];
1830 if (!pwszSearchPath)
1831 pwszSearchPath = (WCHAR *)mkWinChildcareWorkerFindPathValue(pwszzEnv, wszPathFallback);
1832
1833 for (;;)
1834 {
1835 size_t cwcCombined;
1836
1837 /*
1838 * Find the end of the current PATH component.
1839 */
1840 size_t cwcSkip;
1841 WCHAR wcEnd;
1842 size_t cwcComponent = 0;
1843 WCHAR wc;
1844 while ((wc = pwszSearchPath[cwcComponent]) != L'\0')
1845 {
1846 if (wc != ';' && wc != ':')
1847 { /* likely */ }
1848 else if (wc == ';')
1849 break;
1850 else if (cwcComponent != (pwszSearchPath[cwcComponent] != L'"' ? 1 : 2))
1851 break;
1852 cwcComponent++;
1853 }
1854 wcEnd = wc;
1855
1856 /* Trim leading spaces and double quotes. */
1857 while ( cwcComponent > 0
1858 && ((wc = *pwszSearchPath) == L'"' || wc == L' ' || wc == L'\t'))
1859 {
1860 pwszSearchPath++;
1861 cwcComponent--;
1862 }
1863 cwcSkip = cwcComponent;
1864
1865 /* Trim trailing spaces & double quotes. */
1866 while ( cwcComponent > 0
1867 && ((wc = pwszSearchPath[cwcComponent - 1]) == L'"' || wc == L' ' || wc == L'\t'))
1868 cwcComponent--;
1869
1870 /*
1871 * Skip empty components. Join the component and the filename, making sure to
1872 * resolve any CWD relative stuff first.
1873 */
1874 cwcCombined = cwcComponent + 1 + cwcArg0;
1875 if (cwcComponent > 0 && cwcCombined <= MKWINCHILD_MAX_PATH)
1876 {
1877 /* Copy the component into wszPathBuf, maybe abspath'ing it. */
1878 DWORD cwcAbsPath = 0;
1879 if ( *pwszSearchPath != L'\\'
1880 && (pwszSearchPath[1] != ':' || pwszSearchPath[2] != L'\\') )
1881 {
1882 /* To save an extra buffer + copying, we'll temporarily modify the PATH
1883 value in our converted UTF-16 environment block. */
1884 WCHAR const wcSaved = pwszSearchPath[cwcComponent];
1885 pwszSearchPath[cwcComponent] = L'\0';
1886 cwcAbsPath = GetFullPathNameW(pwszSearchPath, MKWINCHILD_MAX_PATH, wszPathBuf, NULL);
1887 pwszSearchPath[cwcComponent] = wcSaved;
1888 if (cwcAbsPath > 0 && cwcAbsPath + 1 + cwcArg0 <= MKWINCHILD_MAX_PATH)
1889 cwcCombined = cwcAbsPath + 1 + cwcArg0;
1890 else
1891 cwcAbsPath = 0;
1892 }
1893 if (cwcAbsPath == 0)
1894 {
1895 memcpy(wszPathBuf, pwszSearchPath, cwcComponent * sizeof(WCHAR));
1896 cwcAbsPath = cwcComponent;
1897 }
1898
1899 /* Append the filename. */
1900 if ((wc = wszPathBuf[cwcAbsPath - 1]) == L'\\' || wc == L'/' || wc == L':')
1901 {
1902 memcpy(&wszPathBuf[cwcAbsPath], wszArg0, cwcArg0 * sizeof(WCHAR));
1903 cwcCombined--;
1904 }
1905 else
1906 {
1907 wszPathBuf[cwcAbsPath] = L'\\';
1908 memcpy(&wszPathBuf[cwcAbsPath + 1], wszArg0, cwcArg0 * sizeof(WCHAR));
1909 }
1910 assert(wszPathBuf[cwcCombined - 1] == L'\0');
1911
1912 /* DOS slash conversion */
1913 pwc = wszPathBuf;
1914 while ((pwc = wcschr(pwc, L'/')) != NULL)
1915 *pwc++ = L'\\';
1916
1917 /*
1918 * Search with exe suffix first.
1919 */
1920 if (!fHasExeSuffix)
1921 {
1922 wszPathBuf[cwcCombined - 1] = L'.';
1923 wszPathBuf[cwcCombined ] = L'e';
1924 wszPathBuf[cwcCombined + 1] = L'x';
1925 wszPathBuf[cwcCombined + 2] = L'e';
1926 wszPathBuf[cwcCombined + 3] = L'\0';
1927 }
1928 if (mkWinChildcareWorkerIsRegularFileW(pWorker, wszPathBuf))
1929 {
1930 *pfProbableClExe = mkWinChildIsProbableClExe(wszPathBuf, cwcCombined + (fHasExeSuffix ? 0 : 4) - 1);
1931 return mkWinChildDuplicateUtf16String(wszPathBuf, cwcCombined + (fHasExeSuffix ? 0 : 4), ppwszImagePath);
1932 }
1933 if (!fHasExeSuffix)
1934 {
1935 wszPathBuf[cwcCombined - 1] = L'\0';
1936#ifdef KMK
1937 if (mkWinChildcareWorkerIsRegularFileW(pWorker, wszPathBuf))
1938#endif
1939 {
1940 /*
1941 * Check if the file exists w/o the added '.exe' suffix. If it does,
1942 * we need to check if we can pass it to CreateProcess or need the shell.
1943 */
1944 hFile = CreateFileW(wszPathBuf, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
1945 NULL /*pSecAttr*/, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1946 if (hFile != INVALID_HANDLE_VALUE)
1947 {
1948 *pfNeedShell = mkWinChildcareWorkerCheckIfNeedShell(hFile);
1949 CloseHandle(hFile);
1950 if (!*pfNeedShell)
1951 {
1952 *pfProbableClExe = mkWinChildIsProbableClExe(wszPathBuf, cwcCombined - 1);
1953 return mkWinChildDuplicateUtf16String(wszPathBuf, cwcCombined, ppwszImagePath);
1954 }
1955 break;
1956 }
1957 }
1958 }
1959 }
1960
1961 /*
1962 * Advance to the next component.
1963 */
1964 if (wcEnd != '\0')
1965 pwszSearchPath += cwcSkip + 1;
1966 else if (fSearchedCwd)
1967 break;
1968 else
1969 {
1970 fSearchedCwd = TRUE;
1971 wszPathFallback[0] = L'.';
1972 wszPathFallback[1] = L'\0';
1973 pwszSearchPath = wszPathFallback;
1974 }
1975 }
1976 }
1977
1978 /*
1979 * We need the shell. It will take care of finding/reporting missing
1980 * image files and such.
1981 */
1982 *pfNeedShell = TRUE;
1983 if (pszShell)
1984 {
1985 cwc = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszShell, strlen(pszShell) + 1, wszPathBuf, MKWINCHILD_MAX_PATH);
1986 if (cwc > 0)
1987 return mkWinChildDuplicateUtf16String(wszPathBuf, cwc, ppwszImagePath);
1988 dwErr = GetLastError();
1989 MkWinChildError(pWorker, 1, _("MultiByteToWideChar failed to convert shell (%s): %u\n"), pszShell, dwErr);
1990 }
1991 else
1992 {
1993 MkWinChildError(pWorker, 1, "%s: not found!\n", pszArg0);
1994 dwErr = ERROR_FILE_NOT_FOUND;
1995 }
1996 }
1997 else
1998 {
1999 dwErr = GetLastError();
2000 MkWinChildError(pWorker, 1, _("MultiByteToWideChar failed to convert argv[0] (%s): %u\n"), pszArg0, dwErr);
2001 }
2002 return dwErr == ERROR_INSUFFICIENT_BUFFER ? ERROR_FILENAME_EXCED_RANGE : dwErr;
2003}
2004
2005/**
2006 * Creates the environment block.
2007 *
2008 * @returns 0 on success, windows error code on failure.
2009 * @param pWorker The childcare worker if on one, otherwise NULL.
2010 * @param papszEnv The environment vector to convert.
2011 * @param cbEnvStrings The size of the environment strings, iff they are
2012 * sequential in a block. Otherwise, zero.
2013 * @param ppwszEnv Where to return the pointer to the environment
2014 * block.
2015 * @param ppwszSearchPath Where to return the pointer to the path value
2016 * within the environment block. This will not be set
2017 * if cbEnvStrings is non-zero, more efficient to let
2018 * mkWinChildcareWorkerFindImage() search when needed.
2019 */
2020static int mkWinChildcareWorkerConvertEnvironment(PWINCHILDCAREWORKER pWorker, char **papszEnv, size_t cbEnvStrings,
2021 WCHAR **ppwszEnv, WCHAR const **ppwszSearchPath)
2022{
2023 DWORD dwErr;
2024 int cwcRc;
2025 int cwcDst;
2026 WCHAR *pwszzDst;
2027
2028 *ppwszSearchPath = NULL;
2029
2030 /*
2031 * We've got a little optimization here with help from mkWinChildCopyStringArray.
2032 */
2033 if (cbEnvStrings)
2034 {
2035 cwcDst = cbEnvStrings + 32;
2036 pwszzDst = (WCHAR *)xmalloc(cwcDst * sizeof(WCHAR));
2037 cwcRc = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, papszEnv[0], cbEnvStrings, pwszzDst, cwcDst);
2038 if (cwcRc != 0)
2039 {
2040 *ppwszEnv = pwszzDst;
2041 return 0;
2042 }
2043
2044 /* Resize the allocation and try again. */
2045 dwErr = GetLastError();
2046 if (dwErr == ERROR_INSUFFICIENT_BUFFER)
2047 {
2048 cwcRc = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, papszEnv[0], cbEnvStrings, NULL, 0);
2049 if (cwcRc > 0)
2050 cwcDst = cwcRc + 32;
2051 else
2052 cwcDst *= 2;
2053 pwszzDst = (WCHAR *)xrealloc(pwszzDst, cwcDst);
2054 cwcRc = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, papszEnv[0], cbEnvStrings, pwszzDst, cwcDst);
2055 if (cwcRc != 0)
2056 {
2057 *ppwszEnv = pwszzDst;
2058 return 0;
2059 }
2060 dwErr = GetLastError();
2061 }
2062 MkWinChildError(pWorker, 1, _("MultiByteToWideChar failed to convert environment block: %u\n"), dwErr);
2063 }
2064 /*
2065 * Need to convert it string by string.
2066 */
2067 else
2068 {
2069 size_t offPathValue = ~(size_t)0;
2070 size_t offDst;
2071
2072 /*
2073 * Estimate the size first.
2074 */
2075 size_t cEnvVars;
2076 size_t cwcDst = 32;
2077 size_t iVar = 0;
2078 const char *pszSrc;
2079 while ((pszSrc = papszEnv[iVar]) != NULL)
2080 {
2081 cwcDst += strlen(pszSrc) + 1;
2082 iVar++;
2083 }
2084 cEnvVars = iVar;
2085
2086 /* Allocate estimated WCHARs and convert the variables one by one, reallocating
2087 the block as needed. */
2088 pwszzDst = (WCHAR *)xmalloc(cwcDst * sizeof(WCHAR));
2089 cwcDst--; /* save one wchar for the terminating empty string. */
2090 offDst = 0;
2091 for (iVar = 0; iVar < cEnvVars; iVar++)
2092 {
2093 size_t cwcLeft = cwcDst - offDst;
2094 size_t const cbSrc = strlen(pszSrc = papszEnv[iVar]) + 1;
2095 assert(cwcDst >= offDst);
2096
2097
2098 cwcRc = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszSrc, cbSrc, &pwszzDst[offDst], cwcLeft);
2099 if (cwcRc > 0)
2100 { /* likely */ }
2101 else
2102 {
2103 dwErr = GetLastError();
2104 if (dwErr == ERROR_INSUFFICIENT_BUFFER)
2105 {
2106 /* Need more space. So, calc exacly how much and resize the block accordingly. */
2107 size_t cbSrc2 = cbSrc;
2108 size_t iVar2 = iVar;
2109 cwcLeft = 1;
2110 for (;;)
2111 {
2112 size_t cwcRc2 = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszSrc, cbSrc, NULL, 0);
2113 if (cwcRc2 > 0)
2114 cwcLeft += cwcRc2;
2115 else
2116 cwcLeft += cbSrc * 4;
2117
2118 /* advance */
2119 iVar2++;
2120 if (iVar2 >= cEnvVars)
2121 break;
2122 pszSrc = papszEnv[iVar2];
2123 cbSrc2 = strlen(pszSrc) + 1;
2124 }
2125 pszSrc = papszEnv[iVar];
2126
2127 /* Grow the allocation and repeat the conversion. */
2128 if (offDst + cwcLeft > cwcDst + 1)
2129 {
2130 cwcDst = offDst + cwcLeft;
2131 pwszzDst = (WCHAR *)xrealloc(pwszzDst, cwcDst * sizeof(WCHAR));
2132 cwcDst--; /* save one wchar for the terminating empty string. */
2133 cwcRc = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszSrc, cbSrc, &pwszzDst[offDst], cwcLeft - 1);
2134 if (cwcRc <= 0)
2135 dwErr = GetLastError();
2136 }
2137 }
2138 if (cwcRc <= 0)
2139 {
2140 MkWinChildError(pWorker, 1, _("MultiByteToWideChar failed to convert environment string #%u (%s): %u\n"),
2141 iVar, pszSrc, dwErr);
2142 free(pwszzDst);
2143 return dwErr;
2144 }
2145 }
2146
2147 /* Look for the PATH. */
2148 if ( offPathValue == ~(size_t)0
2149 && IS_PATH_ENV_VAR(cwcRc, &pwszzDst[offDst]) )
2150 offPathValue = offDst + 4 + 1;
2151
2152 /* Advance. */
2153 offDst += cwcRc;
2154 }
2155 pwszzDst[offDst++] = '\0';
2156
2157 if (offPathValue != ~(size_t)0)
2158 *ppwszSearchPath = &pwszzDst[offPathValue];
2159 *ppwszEnv = pwszzDst;
2160 return 0;
2161 }
2162 free(pwszzDst);
2163 return dwErr;
2164}
2165
2166/**
2167 * Childcare worker: handle regular process.
2168 *
2169 * @param pWorker The worker.
2170 * @param pChild The kSubmit child.
2171 */
2172static void mkWinChildcareWorkerThreadHandleProcess(PWINCHILDCAREWORKER pWorker, PWINCHILD pChild)
2173{
2174 WCHAR *pwszSearchPath = NULL;
2175 WCHAR *pwszzEnvironment = NULL;
2176 WCHAR *pwszCommandLine = NULL;
2177 WCHAR *pwszImageName = NULL;
2178 BOOL fNeedShell = FALSE;
2179 BOOL fProbableClExe = FALSE;
2180 int rc;
2181
2182 /*
2183 * First we convert the environment so we get the PATH we need to
2184 * search for the executable.
2185 */
2186 rc = mkWinChildcareWorkerConvertEnvironment(pWorker, pChild->u.Process.papszEnv ? pChild->u.Process.papszEnv : environ,
2187 pChild->u.Process.cbEnvStrings,
2188 &pwszzEnvironment, &pwszSearchPath);
2189 /*
2190 * Find the executable and maybe checking if it's a shell script, then
2191 * convert it to a command line.
2192 */
2193 if (rc == 0)
2194 rc = mkWinChildcareWorkerFindImage(pWorker, pChild->u.Process.papszArgs[0], pwszSearchPath, pwszzEnvironment,
2195 pChild->u.Process.pszShell, &pwszImageName, &fNeedShell, &pChild->fProbableClExe);
2196 if (rc == 0)
2197 {
2198 if (!fNeedShell)
2199 rc = mkWinChildcareWorkerConvertCommandline(pWorker, pChild->u.Process.papszArgs, 0 /*fFlags*/, &pwszCommandLine);
2200 else
2201 rc = mkWinChildcareWorkerConvertCommandlineWithShell(pWorker, pwszImageName, pChild->u.Process.papszArgs,
2202 &pwszCommandLine);
2203
2204 /*
2205 * Create the child process.
2206 */
2207 if (rc == 0)
2208 {
2209 BOOL afReplace[3] = { FALSE, pChild->u.Process.hStdOut != INVALID_HANDLE_VALUE, pChild->u.Process.hStdErr != INVALID_HANDLE_VALUE };
2210 HANDLE ahChild[3] = { INVALID_HANDLE_VALUE, pChild->u.Process.hStdOut, pChild->u.Process.hStdErr };
2211 rc = mkWinChildcareWorkerCreateProcess(pWorker, pwszImageName, pwszCommandLine, pwszzEnvironment,
2212 NULL /*pwszCwd*/, afReplace, ahChild, pChild->u.Process.fCatchOutput,
2213 &pChild->u.Process.hProcess);
2214 mkWinChildcareWorkerCloseStandardHandles(pChild);
2215 if (rc == 0)
2216 {
2217 /*
2218 * Wait for the child to complete.
2219 */
2220 mkWinChildcareWorkerWaitForProcess(pWorker, pChild, pChild->u.Process.hProcess, pwszImageName,
2221 pChild->u.Process.fCatchOutput);
2222 }
2223 else
2224 pChild->iExitCode = rc;
2225 }
2226 else
2227 pChild->iExitCode = rc;
2228 }
2229 else
2230 pChild->iExitCode = rc;
2231 free(pwszCommandLine);
2232 free(pwszImageName);
2233 free(pwszzEnvironment);
2234
2235 /* In case we failed, we must make sure the child end of pipes
2236 used by $(shell no_such_command.exe) are closed, otherwise
2237 the main thread will be stuck reading the parent end. */
2238 mkWinChildcareWorkerCloseStandardHandles(pChild);
2239}
2240
2241#ifdef KMK
2242
2243/**
2244 * Childcare worker: handle builtin command.
2245 *
2246 * @param pWorker The worker.
2247 * @param pChild The kSubmit child.
2248 */
2249static void mkWinChildcareWorkerThreadHandleBuiltIn(PWINCHILDCAREWORKER pWorker, PWINCHILD pChild)
2250{
2251 PCKMKBUILTINENTRY pBuiltIn = pChild->u.BuiltIn.pBuiltIn;
2252 KMKBUILTINCTX Ctx =
2253 {
2254 pBuiltIn->uName.s.sz,
2255 pChild->pMkChild ? &pChild->pMkChild->output : NULL,
2256 pWorker,
2257 };
2258 if (pBuiltIn->uFnSignature == FN_SIG_MAIN)
2259 pChild->iExitCode = pBuiltIn->u.pfnMain(pChild->u.BuiltIn.cArgs, pChild->u.BuiltIn.papszArgs,
2260 pChild->u.BuiltIn.papszEnv, &Ctx);
2261 else if (pBuiltIn->uFnSignature == FN_SIG_MAIN_SPAWNS)
2262 pChild->iExitCode = pBuiltIn->u.pfnMainSpawns(pChild->u.BuiltIn.cArgs, pChild->u.BuiltIn.papszArgs,
2263 pChild->u.BuiltIn.papszEnv, &Ctx, pChild->pMkChild, NULL /*pPid*/);
2264 else
2265 {
2266 assert(0);
2267 pChild->iExitCode = 98;
2268 }
2269}
2270
2271/**
2272 * Childcare worker: handle append write-out.
2273 *
2274 * @param pWorker The worker.
2275 * @param pChild The kSubmit child.
2276 */
2277static void mkWinChildcareWorkerThreadHandleAppend(PWINCHILDCAREWORKER pWorker, PWINCHILD pChild)
2278{
2279 int fd = open(pChild->u.Append.pszFilename,
2280 pChild->u.Append.fTruncate
2281 ? O_WRONLY | O_TRUNC | O_CREAT | _O_NOINHERIT | _O_BINARY
2282 : O_WRONLY | O_APPEND | O_CREAT | _O_NOINHERIT | _O_BINARY,
2283 0666);
2284 if (fd >= 0)
2285 {
2286 ssize_t cbWritten = write(fd, pChild->u.Append.pszAppend, pChild->u.Append.cbAppend);
2287 if (cbWritten == (ssize_t)pChild->u.Append.cbAppend)
2288 {
2289 if (close(fd) >= 0)
2290 {
2291 pChild->iExitCode = 0;
2292 return;
2293 }
2294 MkWinChildError(pWorker, 1, "kmk_builtin_append: close failed on '%s': %u (%s)\n",
2295 pChild->u.Append.pszFilename, errno, strerror(errno));
2296 }
2297 else
2298 MkWinChildError(pWorker, 1, "kmk_builtin_append: error writing %lu bytes to on '%s': %u (%s)\n",
2299 pChild->u.Append.cbAppend, pChild->u.Append.pszFilename, errno, strerror(errno));
2300 close(fd);
2301 }
2302 else
2303 MkWinChildError(pWorker, 1, "kmk_builtin_append: error opening '%s': %u (%s)\n",
2304 pChild->u.Append.pszFilename, errno, strerror(errno));
2305 pChild->iExitCode = 1;
2306}
2307
2308/**
2309 * Childcare worker: handle kSubmit job.
2310 *
2311 * @param pWorker The worker.
2312 * @param pChild The kSubmit child.
2313 */
2314static void mkWinChildcareWorkerThreadHandleSubmit(PWINCHILDCAREWORKER pWorker, PWINCHILD pChild)
2315{
2316 void *pvSubmitWorker = pChild->u.Submit.pvSubmitWorker;
2317
2318 /*
2319 * Prep the wait handles.
2320 */
2321 HANDLE ahHandles[3] = { pChild->u.Submit.hEvent, NULL, NULL };
2322 DWORD cHandles = 1;
2323 if (pChild->u.Submit.pStdOut)
2324 {
2325 assert(pChild->u.Submit.pStdErr);
2326 pChild->u.Submit.pStdOut->fHaveWrittenOut = FALSE;
2327 ahHandles[cHandles++] = pChild->u.Submit.pStdOut->hPipeMine;
2328 pChild->u.Submit.pStdErr->fHaveWrittenOut = FALSE;
2329 ahHandles[cHandles++] = pChild->u.Submit.pStdErr->hPipeMine;
2330 }
2331
2332 /*
2333 * Wait loop.
2334 */
2335 for (;;)
2336 {
2337 int iExitCode = -42;
2338 int iSignal = -1;
2339 DWORD dwStatus;
2340 if (cHandles == 0)
2341 dwStatus = WaitForSingleObject(ahHandles[0], INFINITE);
2342 else
2343 {
2344 dwStatus = WaitForMultipleObjects(cHandles, ahHandles, FALSE /*fWaitAll*/, INFINITE);
2345 assert(dwStatus != WAIT_FAILED);
2346 if (dwStatus == WAIT_OBJECT_0 + 1)
2347 mkWinChildcareWorkerCatchOutput(pChild, pChild->u.Submit.pStdOut, FALSE /*fDraining*/);
2348 else if (dwStatus == WAIT_OBJECT_0 + 2)
2349 mkWinChildcareWorkerCatchOutput(pChild, pChild->u.Submit.pStdErr, FALSE /*fDraining*/);
2350 }
2351 if (kSubmitSubProcGetResult((intptr_t)pvSubmitWorker, &iExitCode, &iSignal) == 0)
2352 {
2353 if (pChild->u.Submit.pStdOut)
2354 MkWinChildcareWorkerDrainPipes(pChild, pChild->u.Submit.pStdOut, pChild->u.Submit.pStdErr);
2355
2356 pChild->iExitCode = iExitCode;
2357 pChild->iSignal = iSignal;
2358 /* Cleanup must be done on the main thread. */
2359 return;
2360 }
2361
2362 if (pChild->iSignal != 0)
2363 kSubmitSubProcKill((intptr_t)pvSubmitWorker, pChild->iSignal);
2364 }
2365}
2366
2367/**
2368 * Childcare worker: handle kmk_redirect process.
2369 *
2370 * @param pWorker The worker.
2371 * @param pChild The redirect child.
2372 */
2373static void mkWinChildcareWorkerThreadHandleRedirect(PWINCHILDCAREWORKER pWorker, PWINCHILD pChild)
2374{
2375 mkWinChildcareWorkerWaitForProcess(pWorker, pChild, pChild->u.Redirect.hProcess, L"kmk_redirect", FALSE /*fCatchOutput*/);
2376}
2377
2378#endif /* KMK */
2379
2380/**
2381 * Childcare worker thread.
2382 *
2383 * @returns 0
2384 * @param pvUser The worker instance.
2385 */
2386static unsigned int __stdcall mkWinChildcareWorkerThread(void *pvUser)
2387{
2388 PWINCHILDCAREWORKER pWorker = (PWINCHILDCAREWORKER)pvUser;
2389 assert(pWorker->uMagic == WINCHILDCAREWORKER_MAGIC);
2390
2391#ifdef MKWINCHILD_DO_SET_PROCESSOR_GROUP
2392 /*
2393 * Adjust process group if necessary.
2394 *
2395 * Note! It seems that setting the mask to zero means that we select all
2396 * active processors. Couldn't find any alternative API for getting
2397 * the correct active processor mask.
2398 */
2399 if (g_cProcessorGroups > 1)
2400 {
2401 GROUP_AFFINITY Affinity = { 0 /* == all active apparently */ , pWorker->iProcessorGroup, { 0, 0, 0 } };
2402 BOOL fRet = g_pfnSetThreadGroupAffinity(GetCurrentThread(), &Affinity, NULL);
2403 assert(fRet); (void)fRet;
2404# ifndef NDEBUG
2405 {
2406 GROUP_AFFINITY ActualAffinity = { 0xbeefbeefU, 0xbeef, { 0xbeef, 0xbeef, 0xbeef } };
2407 fRet = GetThreadGroupAffinity(GetCurrentThread(), &ActualAffinity);
2408 assert(fRet); (void)fRet;
2409 assert(ActualAffinity.Group == pWorker->iProcessorGroup);
2410 }
2411# endif
2412 }
2413#endif
2414
2415 /*
2416 * Work loop.
2417 */
2418 while (!g_fShutdown)
2419 {
2420 /*
2421 * Try go idle.
2422 */
2423 PWINCHILD pChild = pWorker->pTailTodoChildren;
2424 if (!pChild)
2425 {
2426 _InterlockedExchange(&pWorker->fIdle, TRUE);
2427 pChild = pWorker->pTailTodoChildren;
2428 if (!pChild)
2429 {
2430 DWORD dwStatus;
2431
2432 _InterlockedIncrement((long *)&g_cIdleChildcareWorkers);
2433 _InterlockedExchange((long *)&g_idxLastChildcareWorker, pWorker->idxWorker);
2434 dwStatus = WaitForSingleObject(pWorker->hEvtIdle, INFINITE);
2435 _InterlockedExchange(&pWorker->fIdle, FALSE);
2436 _InterlockedDecrement((long *)&g_cIdleChildcareWorkers);
2437
2438 assert(dwStatus != WAIT_FAILED);
2439 if (dwStatus == WAIT_FAILED)
2440 Sleep(20);
2441
2442 pChild = pWorker->pTailTodoChildren;
2443 }
2444 else
2445 _InterlockedExchange(&pWorker->fIdle, FALSE);
2446 }
2447 if (pChild)
2448 {
2449 /*
2450 * We got work to do. First job is to deque the job.
2451 */
2452 pChild = mkWinChildDequeFromLifo(&pWorker->pTailTodoChildren, pChild);
2453 assert(pChild);
2454 if (pChild)
2455 {
2456 PWINCHILD pTailExpect;
2457
2458 pChild->pWorker = pWorker;
2459 pWorker->pCurChild = pChild;
2460 switch (pChild->enmType)
2461 {
2462 case WINCHILDTYPE_PROCESS:
2463 mkWinChildcareWorkerThreadHandleProcess(pWorker, pChild);
2464 break;
2465#ifdef KMK
2466 case WINCHILDTYPE_BUILT_IN:
2467 mkWinChildcareWorkerThreadHandleBuiltIn(pWorker, pChild);
2468 break;
2469 case WINCHILDTYPE_APPEND:
2470 mkWinChildcareWorkerThreadHandleAppend(pWorker, pChild);
2471 break;
2472 case WINCHILDTYPE_SUBMIT:
2473 mkWinChildcareWorkerThreadHandleSubmit(pWorker, pChild);
2474 break;
2475 case WINCHILDTYPE_REDIRECT:
2476 mkWinChildcareWorkerThreadHandleRedirect(pWorker, pChild);
2477 break;
2478#endif
2479 default:
2480 assert(0);
2481 }
2482 pWorker->pCurChild = NULL;
2483 pChild->pWorker = NULL;
2484
2485 /*
2486 * Move the child to the completed list.
2487 */
2488 pTailExpect = NULL;
2489 for (;;)
2490 {
2491 PWINCHILD pTailActual;
2492 pChild->pNext = pTailExpect;
2493 pTailActual = _InterlockedCompareExchangePointer(&g_pTailCompletedChildren, pChild, pTailExpect);
2494 if (pTailActual != pTailExpect)
2495 pTailExpect = pTailActual;
2496 else
2497 {
2498 _InterlockedDecrement(&g_cPendingChildren);
2499 if (pTailExpect)
2500 break;
2501 if (SetEvent(g_hEvtWaitChildren))
2502 break;
2503 MkWinChildError(pWorker, 1, "SetEvent(g_hEvtWaitChildren=%p) failed: %u\n",
2504 g_hEvtWaitChildren, GetLastError());
2505 break;
2506 }
2507 }
2508 }
2509 }
2510 }
2511
2512 _endthreadex(0);
2513 return 0;
2514}
2515
2516/**
2517 * Creates a pipe for catching child output.
2518 *
2519 * This is a custom CreatePipe implementation that allows for overlapped I/O on
2520 * our end of the pipe. Silly that they don't offer an API that does this.
2521 *
2522 * @returns The pipe that was created. NULL on failure.
2523 * @param pPipe The structure for the pipe.
2524 * @param iWhich Which standard descriptor this is a pipe for.
2525 * @param idxWorker The worker index.
2526 */
2527PWINCCWPIPE MkWinChildcareCreateWorkerPipe(unsigned iWhich, unsigned int idxWorker)
2528{
2529 /*
2530 * We try generate a reasonably unique name from the get go, so this retry
2531 * loop shouldn't really ever be needed. But you never know.
2532 */
2533 static unsigned s_iSeqNo = 0;
2534 DWORD const cMaxInstances = 1;
2535 DWORD const cbPipe = 4096;
2536 DWORD const cMsTimeout = 0;
2537 unsigned cTries = 256;
2538 while (cTries-- > 0)
2539 {
2540 /* Create the pipe (our end). */
2541 HANDLE hPipeRead;
2542 DWORD fOpenMode = PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED | FILE_FLAG_FIRST_PIPE_INSTANCE;
2543 DWORD fPipeMode = PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT | PIPE_REJECT_REMOTE_CLIENTS;
2544 WCHAR wszName[MAX_PATH];
2545 s_iSeqNo++;
2546 _snwprintf(wszName, MAX_PATH, L"\\\\.\\pipe\\kmk-winchildren-%u-%u-%u-%s-%u-%u",
2547 GetCurrentProcessId(), GetCurrentThreadId(), idxWorker, iWhich == 1 ? L"out" : L"err", s_iSeqNo, GetTickCount());
2548 hPipeRead = CreateNamedPipeW(wszName, fOpenMode, fPipeMode, cMaxInstances, cbPipe, cbPipe, cMsTimeout, NULL /*pSecAttr*/);
2549 if (hPipeRead == INVALID_HANDLE_VALUE && GetLastError() == ERROR_INVALID_PARAMETER)
2550 {
2551 fOpenMode &= ~FILE_FLAG_FIRST_PIPE_INSTANCE;
2552 fPipeMode &= ~PIPE_REJECT_REMOTE_CLIENTS;
2553 hPipeRead = CreateNamedPipeW(wszName, fOpenMode, fPipeMode, cMaxInstances, cbPipe, cbPipe, cMsTimeout, NULL /*pSecAttr*/);
2554 }
2555 if (hPipeRead != INVALID_HANDLE_VALUE)
2556 {
2557 /* Connect the other end. */
2558 HANDLE hPipeWrite = CreateFileW(wszName, GENERIC_WRITE | FILE_READ_ATTRIBUTES, 0 /*fShareMode*/, NULL /*pSecAttr*/,
2559 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL /*hTemplateFile*/);
2560 if (hPipeWrite != INVALID_HANDLE_VALUE)
2561 {
2562 /*
2563 * Create the event object and we're done.
2564 *
2565 * It starts in signalled stated so we don't need special code
2566 * for handing when we start waiting.
2567 */
2568 HANDLE hEvent = CreateEventW(NULL /*pSecAttr*/, TRUE /*fManualReset*/, TRUE /*fInitialState*/, NULL /*pwszName*/);
2569 if (hEvent != NULL)
2570 {
2571 PWINCCWPIPE pPipe = (PWINCCWPIPE)xcalloc(sizeof(*pPipe));
2572 pPipe->hPipeMine = hPipeRead;
2573 pPipe->hPipeChild = hPipeWrite;
2574 pPipe->hEvent = hEvent;
2575 pPipe->iWhich = iWhich;
2576 pPipe->fReadPending = FALSE;
2577 pPipe->cbBuffer = cbPipe;
2578 pPipe->pbBuffer = xcalloc(cbPipe);
2579 return pPipe;
2580 }
2581
2582 CloseHandle(hPipeWrite);
2583 CloseHandle(hPipeRead);
2584 return NULL;
2585 }
2586 CloseHandle(hPipeRead);
2587 }
2588 }
2589 return NULL;
2590}
2591
2592/**
2593 * Destroys a childcare worker pipe.
2594 *
2595 * @param pPipe The pipe.
2596 */
2597void MkWinChildcareDeleteWorkerPipe(PWINCCWPIPE pPipe)
2598{
2599 if (pPipe->hPipeChild != NULL)
2600 {
2601 CloseHandle(pPipe->hPipeChild);
2602 pPipe->hPipeChild = NULL;
2603 }
2604
2605 if (pPipe->hPipeMine != NULL)
2606 {
2607 if (pPipe->fReadPending)
2608 if (!CancelIo(pPipe->hPipeMine))
2609 WaitForSingleObject(pPipe->hEvent, INFINITE);
2610 CloseHandle(pPipe->hPipeMine);
2611 pPipe->hPipeMine = NULL;
2612 }
2613
2614 if (pPipe->hEvent != NULL)
2615 {
2616 CloseHandle(pPipe->hEvent);
2617 pPipe->hEvent = NULL;
2618 }
2619
2620 if (pPipe->pbBuffer)
2621 {
2622 free(pPipe->pbBuffer);
2623 pPipe->pbBuffer = NULL;
2624 }
2625}
2626
2627/**
2628 * Creates another childcare worker.
2629 *
2630 * @returns The new worker, if we succeeded.
2631 */
2632static PWINCHILDCAREWORKER mkWinChildcareCreateWorker(void)
2633{
2634 PWINCHILDCAREWORKER pWorker = (PWINCHILDCAREWORKER)xcalloc(sizeof(*pWorker));
2635 pWorker->uMagic = WINCHILDCAREWORKER_MAGIC;
2636 pWorker->idxWorker = g_cChildCareworkers;
2637 pWorker->hEvtIdle = CreateEventW(NULL, FALSE /*fManualReset*/, FALSE /*fInitialState*/, NULL /*pwszName*/);
2638 if (pWorker->hEvtIdle)
2639 {
2640 pWorker->pStdOut = MkWinChildcareCreateWorkerPipe(1, pWorker->idxWorker);
2641 if (pWorker->pStdOut)
2642 {
2643 pWorker->pStdErr = MkWinChildcareCreateWorkerPipe(2, pWorker->idxWorker);
2644 if (pWorker->pStdErr)
2645 {
2646 /* Before we start the thread, assign it to a processor group. */
2647 if (g_cProcessorGroups > 1)
2648 {
2649 unsigned int cMaxInGroup;
2650 unsigned int cInGroup;
2651 unsigned int iGroup = g_idxProcessorGroupAllocator % g_cProcessorGroups;
2652 pWorker->iProcessorGroup = iGroup;
2653
2654 /* Advance. We employ a very simple strategy that does 50% in
2655 each group for each group cycle. Odd processor counts are
2656 caught in odd group cycles. The init function selects the
2657 starting group based on make nesting level to avoid stressing
2658 out the first group. */
2659 cInGroup = ++g_idxProcessorInGroupAllocator;
2660 cMaxInGroup = g_pacProcessorsInGroup[iGroup];
2661 if ( !(cMaxInGroup & 1)
2662 || !((g_idxProcessorGroupAllocator / g_cProcessorGroups) & 1))
2663 cMaxInGroup /= 2;
2664 else
2665 cMaxInGroup = cMaxInGroup / 2 + 1;
2666 if (cInGroup >= cMaxInGroup)
2667 {
2668 g_idxProcessorInGroupAllocator = 0;
2669 g_idxProcessorGroupAllocator++;
2670 }
2671 }
2672
2673 /* Try start the thread. */
2674 pWorker->hThread = (HANDLE)_beginthreadex(NULL, 0 /*cbStack*/, mkWinChildcareWorkerThread, pWorker,
2675 0, &pWorker->tid);
2676 if (pWorker->hThread != NULL)
2677 {
2678 pWorker->idxWorker = g_cChildCareworkers++; /* paranoia */
2679 g_papChildCareworkers[pWorker->idxWorker] = pWorker;
2680 return pWorker;
2681 }
2682
2683 /* Bail out! */
2684 ONS (error, NILF, "_beginthreadex failed: %u (%s)\n", errno, strerror(errno));
2685 MkWinChildcareDeleteWorkerPipe(pWorker->pStdErr);
2686 }
2687 else
2688 ON (error, NILF, "Failed to create stderr pipe: %u\n", GetLastError());
2689 MkWinChildcareDeleteWorkerPipe(pWorker->pStdOut);
2690 }
2691 else
2692 ON (error, NILF, "Failed to create stdout pipe: %u\n", GetLastError());
2693 CloseHandle(pWorker->hEvtIdle);
2694 }
2695 else
2696 ON (error, NILF, "CreateEvent failed: %u\n", GetLastError());
2697 pWorker->uMagic = ~WINCHILDCAREWORKER_MAGIC;
2698 free(pWorker);
2699 return NULL;
2700}
2701
2702/**
2703 * Helper for copying argument and environment vectors.
2704 *
2705 * @returns Single alloc block copy.
2706 * @param papszSrc The source vector.
2707 * @param pcbStrings Where to return the size of the strings & terminator.
2708 */
2709static char **mkWinChildCopyStringArray(char **papszSrc, size_t *pcbStrings)
2710{
2711 const char *psz;
2712 char **papszDstArray;
2713 char *pszDstStr;
2714 size_t i;
2715
2716 /* Calc sizes first. */
2717 size_t cbStrings = 1; /* (one extra for terminator string) */
2718 size_t cStrings = 0;
2719 while ((psz = papszSrc[cStrings]) != NULL)
2720 {
2721 cbStrings += strlen(psz) + 1;
2722 cStrings++;
2723 }
2724 *pcbStrings = cbStrings;
2725
2726 /* Allocate destination. */
2727 papszDstArray = (char **)xmalloc(cbStrings + (cStrings + 1) * sizeof(papszDstArray[0]));
2728 pszDstStr = (char *)&papszDstArray[cStrings + 1];
2729
2730 /* Copy it. */
2731 for (i = 0; i < cStrings; i++)
2732 {
2733 const char *pszSource = papszSrc[i];
2734 size_t cchString = strlen(pszSource);
2735 papszDstArray[i] = pszDstStr;
2736 memcpy(pszDstStr, pszSource, cchString);
2737 pszDstStr += cchString;
2738 *pszDstStr++ = '\0';
2739 }
2740 *pszDstStr = '\0';
2741 assert(&pszDstStr[1] - papszDstArray[0] == cbStrings);
2742 papszDstArray[i] = NULL;
2743 return papszDstArray;
2744}
2745
2746/**
2747 * Allocate and init a WINCHILD.
2748 *
2749 * @returns The new windows child structure.
2750 * @param enmType The child type.
2751 */
2752static PWINCHILD mkWinChildNew(WINCHILDTYPE enmType)
2753{
2754 PWINCHILD pChild = xcalloc(sizeof(*pChild));
2755 pChild->enmType = enmType;
2756 pChild->fCoreDumped = 0;
2757 pChild->iSignal = 0;
2758 pChild->iExitCode = 222222;
2759 pChild->uMagic = WINCHILD_MAGIC;
2760 pChild->pid = (intptr_t)pChild;
2761 return pChild;
2762}
2763
2764/**
2765 * Destructor for WINCHILD.
2766 *
2767 * @param pChild The child structure to destroy.
2768 */
2769static void mkWinChildDelete(PWINCHILD pChild)
2770{
2771 assert(pChild->uMagic == WINCHILD_MAGIC);
2772 pChild->uMagic = ~WINCHILD_MAGIC;
2773
2774 switch (pChild->enmType)
2775 {
2776 case WINCHILDTYPE_PROCESS:
2777 {
2778 if (pChild->u.Process.papszArgs)
2779 {
2780 free(pChild->u.Process.papszArgs);
2781 pChild->u.Process.papszArgs = NULL;
2782 }
2783 if (pChild->u.Process.cbEnvStrings && pChild->u.Process.papszEnv)
2784 {
2785 free(pChild->u.Process.papszEnv);
2786 pChild->u.Process.papszEnv = NULL;
2787 }
2788 if (pChild->u.Process.pszShell)
2789 {
2790 free(pChild->u.Process.pszShell);
2791 pChild->u.Process.pszShell = NULL;
2792 }
2793 if (pChild->u.Process.hProcess)
2794 {
2795 CloseHandle(pChild->u.Process.hProcess);
2796 pChild->u.Process.hProcess = NULL;
2797 }
2798 mkWinChildcareWorkerCloseStandardHandles(pChild);
2799 break;
2800 }
2801
2802#ifdef KMK
2803 case WINCHILDTYPE_BUILT_IN:
2804 if (pChild->u.BuiltIn.papszArgs)
2805 {
2806 free(pChild->u.BuiltIn.papszArgs);
2807 pChild->u.BuiltIn.papszArgs = NULL;
2808 }
2809 if (pChild->u.BuiltIn.papszEnv)
2810 {
2811 free(pChild->u.BuiltIn.papszEnv);
2812 pChild->u.BuiltIn.papszEnv = NULL;
2813 }
2814 break;
2815
2816 case WINCHILDTYPE_APPEND:
2817 if (pChild->u.Append.pszFilename)
2818 {
2819 free(pChild->u.Append.pszFilename);
2820 pChild->u.Append.pszFilename = NULL;
2821 }
2822 if (pChild->u.Append.pszAppend)
2823 {
2824 free(pChild->u.Append.pszAppend);
2825 pChild->u.Append.pszAppend = NULL;
2826 }
2827 break;
2828
2829 case WINCHILDTYPE_SUBMIT:
2830 if (pChild->u.Submit.pvSubmitWorker)
2831 {
2832 kSubmitSubProcCleanup((intptr_t)pChild->u.Submit.pvSubmitWorker);
2833 pChild->u.Submit.pvSubmitWorker = NULL;
2834 }
2835 break;
2836
2837 case WINCHILDTYPE_REDIRECT:
2838 if (pChild->u.Redirect.hProcess)
2839 {
2840 CloseHandle(pChild->u.Redirect.hProcess);
2841 pChild->u.Redirect.hProcess = NULL;
2842 }
2843 break;
2844#endif /* KMK */
2845
2846 default:
2847 assert(0);
2848 }
2849
2850 free(pChild);
2851}
2852
2853/**
2854 * Queues the child with a worker, creating new workers if necessary.
2855 *
2856 * @returns 0 on success, windows error code on failure (child destroyed).
2857 * @param pChild The child.
2858 * @param pPid Where to return the PID (optional).
2859 */
2860static int mkWinChildPushToCareWorker(PWINCHILD pChild, pid_t *pPid)
2861{
2862 PWINCHILDCAREWORKER pWorker = NULL;
2863 PWINCHILD pOldChild;
2864 PWINCHILD pCurChild;
2865
2866 /*
2867 * There are usually idle workers around, except for at the start.
2868 */
2869 if (g_cIdleChildcareWorkers > 0)
2870 {
2871 /*
2872 * Try the idle hint first and move forward from it.
2873 */
2874 unsigned int const cWorkers = g_cChildCareworkers;
2875 unsigned int iHint = g_idxLastChildcareWorker;
2876 unsigned int i;
2877 for (i = iHint; i < cWorkers; i++)
2878 {
2879 PWINCHILDCAREWORKER pPossibleWorker = g_papChildCareworkers[i];
2880 if (pPossibleWorker->fIdle)
2881 {
2882 pWorker = pPossibleWorker;
2883 break;
2884 }
2885 }
2886 if (!pWorker)
2887 {
2888 /* Scan from the start. */
2889 if (iHint > cWorkers)
2890 iHint = cWorkers;
2891 for (i = 0; i < iHint; i++)
2892 {
2893 PWINCHILDCAREWORKER pPossibleWorker = g_papChildCareworkers[i];
2894 if (pPossibleWorker->fIdle)
2895 {
2896 pWorker = pPossibleWorker;
2897 break;
2898 }
2899 }
2900 }
2901 }
2902 if (!pWorker)
2903 {
2904 /*
2905 * Try create more workers if we haven't reached the max yet.
2906 */
2907 if (g_cChildCareworkers < g_cChildCareworkersMax)
2908 pWorker = mkWinChildcareCreateWorker();
2909
2910 /*
2911 * Queue it with an existing worker. Look for one without anthing extra scheduled.
2912 */
2913 if (!pWorker)
2914 {
2915 unsigned int i = g_cChildCareworkers;
2916 if (i == 0)
2917 fatal(NILF, 0, _("Failed to create worker threads for managing child processes!\n"));
2918 pWorker = g_papChildCareworkers[--i];
2919 if (pWorker->pTailTodoChildren)
2920 while (i-- > 0)
2921 {
2922 PWINCHILDCAREWORKER pPossibleWorker = g_papChildCareworkers[i];
2923 if (!pPossibleWorker->pTailTodoChildren)
2924 {
2925 pWorker = pPossibleWorker;
2926 break;
2927 }
2928 }
2929 }
2930 }
2931
2932 /*
2933 * Do the queueing.
2934 */
2935 pOldChild = NULL;
2936 for (;;)
2937 {
2938 pChild->pNext = pOldChild;
2939 pCurChild = _InterlockedCompareExchangePointer((void **)&pWorker->pTailTodoChildren, pChild, pOldChild);
2940 if (pCurChild == pOldChild)
2941 {
2942 DWORD volatile dwErr;
2943 _InterlockedIncrement(&g_cPendingChildren);
2944 if ( !pWorker->fIdle
2945 || SetEvent(pWorker->hEvtIdle))
2946 {
2947 *pPid = pChild->pid;
2948 return 0;
2949 }
2950
2951 _InterlockedDecrement(&g_cPendingChildren);
2952 dwErr = GetLastError();
2953 assert(0);
2954 mkWinChildDelete(pChild);
2955 return dwErr ? dwErr : -20;
2956 }
2957 pOldChild = pCurChild;
2958 }
2959}
2960
2961/**
2962 * Creates a regular child process (job.c).
2963 *
2964 * Will copy the information and push it to a childcare thread that does the
2965 * actual process creation.
2966 *
2967 * @returns 0 on success, windows status code on failure.
2968 * @param papszArgs The arguments.
2969 * @param papszEnv The environment (optional).
2970 * @param pszShell The SHELL variable value (optional).
2971 * @param pMkChild The make child structure (optional).
2972 * @param pPid Where to return the pid.
2973 */
2974int MkWinChildCreate(char **papszArgs, char **papszEnv, const char *pszShell, struct child *pMkChild, pid_t *pPid)
2975{
2976 PWINCHILD pChild = mkWinChildNew(WINCHILDTYPE_PROCESS);
2977 pChild->pMkChild = pMkChild;
2978
2979 pChild->u.Process.papszArgs = mkWinChildCopyStringArray(papszArgs, &pChild->u.Process.cbArgsStrings);
2980 if ( !papszEnv
2981 || !pMkChild
2982 || pMkChild->environment == papszEnv)
2983 {
2984 pChild->u.Process.cbEnvStrings = 0;
2985 pChild->u.Process.papszEnv = papszEnv;
2986 }
2987 else
2988 pChild->u.Process.papszEnv = mkWinChildCopyStringArray(papszEnv, &pChild->u.Process.cbEnvStrings);
2989 if (pszShell)
2990 pChild->u.Process.pszShell = xstrdup(pszShell);
2991 pChild->u.Process.hStdOut = INVALID_HANDLE_VALUE;
2992 pChild->u.Process.hStdErr = INVALID_HANDLE_VALUE;
2993
2994 /* We always catch the output in order to prevent character soups courtesy
2995 of the microsoft CRT and/or linkers writing character by character to the
2996 console. Always try write whole lines, even when --output-sync is none. */
2997 pChild->u.Process.fCatchOutput = TRUE;
2998
2999 return mkWinChildPushToCareWorker(pChild, pPid);
3000}
3001
3002/**
3003 * Creates a chile process with a pipe hooked up to stdout.
3004 *
3005 * @returns 0 on success, non-zero on failure.
3006 * @param papszArgs The argument vector.
3007 * @param papszEnv The environment vector (optional).
3008 * @param fdErr File descriptor to hook up to stderr.
3009 * @param pPid Where to return the pid.
3010 * @param pfdReadPipe Where to return the read end of the pipe.
3011 */
3012int MkWinChildCreateWithStdOutPipe(char **papszArgs, char **papszEnv, int fdErr, pid_t *pPid, int *pfdReadPipe)
3013{
3014 /*
3015 * Create the pipe.
3016 */
3017 HANDLE hReadPipe;
3018 HANDLE hWritePipe;
3019 if (CreatePipe(&hReadPipe, &hWritePipe, NULL, 0 /* default size */))
3020 {
3021 //if (SetHandleInformation(hWritePipe, HANDLE_FLAG_INHERIT /* clear */ , HANDLE_FLAG_INHERIT /*set*/))
3022 {
3023 int fdReadPipe = _open_osfhandle((intptr_t)hReadPipe, O_RDONLY);
3024 if (fdReadPipe >= 0)
3025 {
3026 PWINCHILD pChild;
3027 int rc;
3028
3029 /*
3030 * Get a handle for fdErr. Ignore failure.
3031 */
3032 HANDLE hStdErr = INVALID_HANDLE_VALUE;
3033 if (fdErr >= 0)
3034 {
3035 HANDLE hNative = (HANDLE)_get_osfhandle(fdErr);
3036 if (!DuplicateHandle(GetCurrentProcess(), hNative, GetCurrentProcess(),
3037 &hStdErr, 0 /*DesiredAccess*/, TRUE /*fInherit*/, DUPLICATE_SAME_ACCESS))
3038 {
3039 ONN(error, NILF, _("DuplicateHandle failed on stderr descriptor (%u): %u\n"), fdErr, GetLastError());
3040 hStdErr = INVALID_HANDLE_VALUE;
3041 }
3042 }
3043
3044 /*
3045 * Push it off to the worker thread.
3046 */
3047 pChild = mkWinChildNew(WINCHILDTYPE_PROCESS);
3048 pChild->u.Process.papszArgs = mkWinChildCopyStringArray(papszArgs, &pChild->u.Process.cbArgsStrings);
3049 pChild->u.Process.papszEnv = mkWinChildCopyStringArray(papszEnv ? papszEnv : environ,
3050 &pChild->u.Process.cbEnvStrings);
3051 //if (pszShell)
3052 // pChild->u.Process.pszShell = xstrdup(pszShell);
3053 pChild->u.Process.hStdOut = hWritePipe;
3054 pChild->u.Process.hStdErr = hStdErr;
3055 pChild->u.Process.fCloseStdErr = TRUE;
3056 pChild->u.Process.fCloseStdOut = TRUE;
3057
3058 rc = mkWinChildPushToCareWorker(pChild, pPid);
3059 if (rc == 0)
3060 *pfdReadPipe = fdReadPipe;
3061 else
3062 {
3063 ON(error, NILF, _("mkWinChildPushToCareWorker failed on pipe: %d\n"), rc);
3064 close(fdReadPipe);
3065 *pfdReadPipe = -1;
3066 *pPid = -1;
3067 }
3068 return rc;
3069 }
3070
3071 ON(error, NILF, _("_open_osfhandle failed on pipe: %u\n"), errno);
3072 }
3073 //else
3074 // ON(error, NILF, _("SetHandleInformation failed on pipe: %u\n"), GetLastError());
3075 if (hReadPipe != INVALID_HANDLE_VALUE)
3076 CloseHandle(hReadPipe);
3077 CloseHandle(hWritePipe);
3078 }
3079 else
3080 ON(error, NILF, _("CreatePipe failed: %u\n"), GetLastError());
3081 *pfdReadPipe = -1;
3082 *pPid = -1;
3083 return -1;
3084}
3085
3086#ifdef KMK
3087
3088/**
3089 * Interface used by kmkbuiltin.c for executing builtin commands on threads.
3090 *
3091 * @returns 0 on success, windows status code on failure.
3092 * @param pBuiltIn The kmk built-in command entry.
3093 * @param cArgs The number of arguments in papszArgs.
3094 * @param papszArgs The argument vector.
3095 * @param papszEnv The environment vector, optional.
3096 * @param pMkChild The make child structure.
3097 * @param pPid Where to return the pid.
3098 */
3099int MkWinChildCreateBuiltIn(PCKMKBUILTINENTRY pBuiltIn, int cArgs, char **papszArgs, char **papszEnv,
3100 struct child *pMkChild, pid_t *pPid)
3101{
3102 size_t cbIgnored;
3103 PWINCHILD pChild = mkWinChildNew(WINCHILDTYPE_BUILT_IN);
3104 pChild->pMkChild = pMkChild;
3105 pChild->u.BuiltIn.pBuiltIn = pBuiltIn;
3106 pChild->u.BuiltIn.cArgs = cArgs;
3107 pChild->u.BuiltIn.papszArgs = mkWinChildCopyStringArray(papszArgs, &cbIgnored);
3108 pChild->u.BuiltIn.papszEnv = papszEnv ? mkWinChildCopyStringArray(papszEnv, &cbIgnored) : NULL;
3109 return mkWinChildPushToCareWorker(pChild, pPid);
3110}
3111
3112/**
3113 * Interface used by append.c for do the slow file system part.
3114 *
3115 * This will append the given buffer to the specified file and free the buffer.
3116 *
3117 * @returns 0 on success, windows status code on failure.
3118 *
3119 * @param pszFilename The name of the file to append to.
3120 * @param ppszAppend What to append. The pointer pointed to is set to
3121 * NULL once we've taken ownership of the buffer and
3122 * promise to free it.
3123 * @param cbAppend How much to append.
3124 * @param fTruncate Whether to truncate the file before appending to it.
3125 * @param pMkChild The make child structure.
3126 * @param pPid Where to return the pid.
3127 */
3128int MkWinChildCreateAppend(const char *pszFilename, char **ppszAppend, size_t cbAppend, int fTruncate,
3129 struct child *pMkChild, pid_t *pPid)
3130{
3131 size_t cbFilename = strlen(pszFilename) + 1;
3132 PWINCHILD pChild = mkWinChildNew(WINCHILDTYPE_APPEND);
3133 pChild->pMkChild = pMkChild;
3134 pChild->u.Append.fTruncate = fTruncate;
3135 pChild->u.Append.pszAppend = *ppszAppend;
3136 pChild->u.Append.cbAppend = cbAppend;
3137 pChild->u.Append.pszFilename = (char *)memcpy(xmalloc(cbFilename), pszFilename, cbFilename);
3138 *ppszAppend = NULL;
3139 return mkWinChildPushToCareWorker(pChild, pPid);
3140}
3141
3142/**
3143 * Interface used by kSubmit.c for registering stuff to wait on.
3144 *
3145 * @returns 0 on success, windows status code on failure.
3146 * @param hEvent The event object handle to wait on.
3147 * @param pvSubmitWorker The argument to pass back to kSubmit to clean up.
3148 * @param pStdOut Standard output pipe for the worker. Optional.
3149 * @param pStdErr Standard error pipe for the worker. Optional.
3150 * @param pMkChild The make child structure.
3151 * @param pPid Where to return the pid.
3152 */
3153int MkWinChildCreateSubmit(intptr_t hEvent, void *pvSubmitWorker, PWINCCWPIPE pStdOut, PWINCCWPIPE pStdErr,
3154 struct child *pMkChild, pid_t *pPid)
3155{
3156 PWINCHILD pChild = mkWinChildNew(WINCHILDTYPE_SUBMIT);
3157 pChild->pMkChild = pMkChild;
3158 pChild->u.Submit.hEvent = (HANDLE)hEvent;
3159 pChild->u.Submit.pvSubmitWorker = pvSubmitWorker;
3160 pChild->u.Submit.pStdOut = pStdOut;
3161 pChild->u.Submit.pStdErr = pStdErr;
3162 return mkWinChildPushToCareWorker(pChild, pPid);
3163}
3164
3165/**
3166 * Interface used by redirect.c for registering stuff to wait on.
3167 *
3168 * @returns 0 on success, windows status code on failure.
3169 * @param hProcess The process object to wait on.
3170 * @param pPid Where to return the pid.
3171 */
3172int MkWinChildCreateRedirect(intptr_t hProcess, pid_t *pPid)
3173{
3174 PWINCHILD pChild = mkWinChildNew(WINCHILDTYPE_REDIRECT);
3175 pChild->u.Redirect.hProcess = (HANDLE)hProcess;
3176 return mkWinChildPushToCareWorker(pChild, pPid);
3177}
3178
3179
3180/**
3181 * New interface used by redirect.c for spawning and waitin on a child.
3182 *
3183 * This interface is only used when kmk_builtin_redirect is already running on
3184 * a worker thread.
3185 *
3186 * @returns exit status.
3187 * @param pvWorker The worker instance.
3188 * @param pszExecutable The executable image to run.
3189 * @param papszArgs Argument vector.
3190 * @param fQuotedArgv Whether the argument vector is already quoted and
3191 * just need some space to be turned into a command
3192 * line.
3193 * @param papszEnvVars Environment vector.
3194 * @param pszCwd The working directory of the child. Optional.
3195 * @param pafReplace Which standard handles to replace. Maybe modified!
3196 * @param pahReplace The replacement handles. Maybe modified!
3197 *
3198 */
3199int MkWinChildBuiltInExecChild(void *pvWorker, const char *pszExecutable, char **papszArgs, BOOL fQuotedArgv,
3200 char **papszEnvVars, const char *pszCwd, BOOL pafReplace[3], HANDLE pahReplace[3])
3201{
3202 PWINCHILDCAREWORKER pWorker = (PWINCHILDCAREWORKER)pvWorker;
3203 WCHAR *pwszSearchPath = NULL;
3204 WCHAR *pwszzEnvironment = NULL;
3205 WCHAR *pwszCommandLine = NULL;
3206 WCHAR *pwszImageName = NULL;
3207 WCHAR *pwszCwd = NULL;
3208 BOOL fNeedShell = FALSE;
3209 PWINCHILD pChild;
3210 int rc;
3211 assert(pWorker->uMagic == WINCHILDCAREWORKER_MAGIC);
3212 pChild = pWorker->pCurChild;
3213 assert(pChild != NULL && pChild->uMagic == WINCHILD_MAGIC);
3214
3215 /*
3216 * Convert the CWD first since it's optional and we don't need to clean
3217 * up anything here if it fails.
3218 */
3219 if (pszCwd)
3220 {
3221 size_t cchCwd = strlen(pszCwd);
3222 int cwcCwd = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszCwd, cchCwd + 1, NULL, 0);
3223 pwszCwd = xmalloc((cwcCwd + 1) * sizeof(WCHAR)); /* (+1 in case cwcCwd is 0) */
3224 cwcCwd = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszCwd, cchCwd + 1, pwszCwd, cwcCwd + 1);
3225 if (!cwcCwd)
3226 {
3227 rc = GetLastError();
3228 MkWinChildError(pWorker, 1, _("MultiByteToWideChar failed to convert CWD (%s): %u\n"), pszCwd, (unsigned)rc);
3229 return rc;
3230 }
3231 }
3232
3233 /*
3234 * Before we search for the image, we convert the environment so we don't
3235 * have to traverse it twice to find the PATH.
3236 */
3237 rc = mkWinChildcareWorkerConvertEnvironment(pWorker, papszEnvVars ? papszEnvVars : environ, 0/*cbEnvStrings*/,
3238 &pwszzEnvironment, &pwszSearchPath);
3239 /*
3240 * Find the executable and maybe checking if it's a shell script, then
3241 * convert it to a command line.
3242 */
3243 if (rc == 0)
3244 rc = mkWinChildcareWorkerFindImage(pWorker, pszExecutable, pwszSearchPath, pwszzEnvironment, NULL /*pszShell*/,
3245 &pwszImageName, &fNeedShell, &pChild->fProbableClExe);
3246 if (rc == 0)
3247 {
3248 assert(!fNeedShell);
3249 if (!fQuotedArgv)
3250 rc = mkWinChildcareWorkerConvertCommandline(pWorker, papszArgs, 0 /*fFlags*/, &pwszCommandLine);
3251 else
3252 rc = mkWinChildcareWorkerConvertQuotedArgvToCommandline(pWorker, papszArgs, &pwszCommandLine);
3253
3254 /*
3255 * Create the child process.
3256 */
3257 if (rc == 0)
3258 {
3259 HANDLE hProcess;
3260 rc = mkWinChildcareWorkerCreateProcess(pWorker, pwszImageName, pwszCommandLine, pwszzEnvironment,
3261 pwszCwd, pafReplace, pahReplace, TRUE /*fCatchOutput*/, &hProcess);
3262 if (rc == 0)
3263 {
3264 /*
3265 * Wait for the child to complete.
3266 */
3267 rc = mkWinChildcareWorkerWaitForProcess(pWorker, pChild, hProcess, pwszImageName, TRUE /*fCatchOutput*/);
3268 CloseHandle(hProcess);
3269 }
3270 }
3271 }
3272
3273 free(pwszCwd);
3274 free(pwszCommandLine);
3275 free(pwszImageName);
3276 free(pwszzEnvironment);
3277
3278 return rc;
3279}
3280
3281#endif /* CONFIG_NEW_WIN_CHILDREN */
3282
3283/**
3284 * Interface used to kill process when processing Ctrl-C and fatal errors.
3285 *
3286 * @returns 0 on success, -1 & errno on error.
3287 * @param pid The process to kill (PWINCHILD).
3288 * @param iSignal What to kill it with.
3289 * @param pMkChild The make child structure for validation.
3290 */
3291int MkWinChildKill(pid_t pid, int iSignal, struct child *pMkChild)
3292{
3293 PWINCHILD pChild = (PWINCHILD)pid;
3294 if (pChild)
3295 {
3296 assert(pChild->uMagic == WINCHILD_MAGIC);
3297 if (pChild->uMagic == WINCHILD_MAGIC)
3298 {
3299 switch (pChild->enmType)
3300 {
3301 case WINCHILDTYPE_PROCESS:
3302 assert(pChild->pMkChild == pMkChild);
3303 TerminateProcess(pChild->u.Process.hProcess, DBG_TERMINATE_PROCESS);
3304 pChild->iSignal = iSignal;
3305 break;
3306#ifdef KMK
3307 case WINCHILDTYPE_SUBMIT:
3308 {
3309 pChild->iSignal = iSignal;
3310 SetEvent(pChild->u.Submit.hEvent);
3311 break;
3312 }
3313
3314 case WINCHILDTYPE_REDIRECT:
3315 TerminateProcess(pChild->u.Redirect.hProcess, DBG_TERMINATE_PROCESS);
3316 pChild->iSignal = iSignal;
3317 break;
3318
3319 case WINCHILDTYPE_BUILT_IN:
3320 break;
3321
3322#endif /* KMK */
3323 default:
3324 assert(0);
3325 }
3326 }
3327 }
3328 return -1;
3329}
3330
3331/**
3332 * Wait for a child process to complete
3333 *
3334 * @returns 0 on success, windows error code on failure.
3335 * @param fBlock Whether to block.
3336 * @param pPid Where to return the pid if a child process
3337 * completed. This is set to zero if none.
3338 * @param piExitCode Where to return the exit code.
3339 * @param piSignal Where to return the exit signal number.
3340 * @param pfCoreDumped Where to return the core dumped indicator.
3341 * @param ppMkChild Where to return the associated struct child pointer.
3342 */
3343int MkWinChildWait(int fBlock, pid_t *pPid, int *piExitCode, int *piSignal, int *pfCoreDumped, struct child **ppMkChild)
3344{
3345 PWINCHILD pChild;
3346
3347 *pPid = 0;
3348 *piExitCode = -222222;
3349 *pfCoreDumped = 0;
3350 *ppMkChild = NULL;
3351
3352 /*
3353 * Wait if necessary.
3354 */
3355 if (fBlock && !g_pTailCompletedChildren && g_cPendingChildren > 0)
3356 {
3357 DWORD dwStatus = WaitForSingleObject(g_hEvtWaitChildren, INFINITE);
3358 if (dwStatus == WAIT_FAILED)
3359 return (int)GetLastError();
3360 }
3361
3362 /*
3363 * Try unlink the last child in the LIFO.
3364 */
3365 pChild = g_pTailCompletedChildren;
3366 if (!pChild)
3367 return 0;
3368 pChild = mkWinChildDequeFromLifo(&g_pTailCompletedChildren, pChild);
3369 assert(pChild);
3370
3371 /*
3372 * Set return values and ditch the child structure.
3373 */
3374 *pPid = pChild->pid;
3375 *piExitCode = pChild->iExitCode;
3376 *pfCoreDumped = pChild->fCoreDumped;
3377 *ppMkChild = pChild->pMkChild;
3378 switch (pChild->enmType)
3379 {
3380 case WINCHILDTYPE_PROCESS:
3381 break;
3382#ifdef KMK
3383 case WINCHILDTYPE_BUILT_IN:
3384 case WINCHILDTYPE_APPEND:
3385 case WINCHILDTYPE_SUBMIT:
3386 case WINCHILDTYPE_REDIRECT:
3387 break;
3388#endif /* KMK */
3389 default:
3390 assert(0);
3391 }
3392 mkWinChildDelete(pChild);
3393
3394#ifdef KMK
3395 /* Flush the volatile directory cache. */
3396 dir_cache_invalid_after_job();
3397#endif
3398 return 0;
3399}
3400
3401/**
3402 * Get the child completed event handle.
3403 *
3404 * Needed when w32os.c is waiting for a job token to become available, given
3405 * that completed children is the typical source of these tokens (esp. for kmk).
3406 *
3407 * @returns Zero if completed children, event handle if waiting is required.
3408 */
3409intptr_t MkWinChildGetCompleteEventHandle(void)
3410{
3411 /* We don't return the handle if we've got completed children. This
3412 is a safe guard against being called twice in a row without any
3413 MkWinChildWait call inbetween. */
3414 if (!g_pTailCompletedChildren)
3415 return (intptr_t)g_hEvtWaitChildren;
3416 return 0;
3417}
3418
3419/**
3420 * Emulate execv() for restarting kmk after one ore more makefiles has been
3421 * made.
3422 *
3423 * Does not return.
3424 *
3425 * @param papszArgs The arguments.
3426 * @param papszEnv The environment.
3427 */
3428void MkWinChildReExecMake(char **papszArgs, char **papszEnv)
3429{
3430 PROCESS_INFORMATION ProcInfo;
3431 STARTUPINFOW StartupInfo;
3432 WCHAR *pwszCommandLine;
3433 WCHAR *pwszzEnvironment;
3434 WCHAR *pwszPathIgnored;
3435 int rc;
3436
3437 /*
3438 * Get the executable name.
3439 */
3440 WCHAR wszImageName[MKWINCHILD_MAX_PATH];
3441 DWORD cwcImageName = GetModuleFileNameW(GetModuleHandle(NULL), wszImageName, MKWINCHILD_MAX_PATH);
3442 if (cwcImageName == 0)
3443 ON(fatal, NILF, _("MkWinChildReExecMake: GetModuleFileName failed: %u\n"), GetLastError());
3444
3445 /*
3446 * Create the command line and environment.
3447 */
3448 rc = mkWinChildcareWorkerConvertCommandline(NULL, papszArgs, 0 /*fFlags*/, &pwszCommandLine);
3449 if (rc != 0)
3450 ON(fatal, NILF, _("MkWinChildReExecMake: mkWinChildcareWorkerConvertCommandline failed: %u\n"), rc);
3451
3452 rc = mkWinChildcareWorkerConvertEnvironment(NULL, papszEnv ? papszEnv : environ, 0 /*cbEnvStrings*/,
3453 &pwszzEnvironment, &pwszPathIgnored);
3454 if (rc != 0)
3455 ON(fatal, NILF, _("MkWinChildReExecMake: mkWinChildcareWorkerConvertEnvironment failed: %u\n"), rc);
3456
3457
3458 /*
3459 * Fill out the startup info and try create the process.
3460 */
3461 memset(&ProcInfo, 0, sizeof(ProcInfo));
3462 memset(&StartupInfo, 0, sizeof(StartupInfo));
3463 StartupInfo.cb = sizeof(StartupInfo);
3464 GetStartupInfoW(&StartupInfo);
3465 if (!CreateProcessW(wszImageName, pwszCommandLine, NULL /*pProcSecAttr*/, NULL /*pThreadSecAttr*/,
3466 TRUE /*fInheritHandles*/, CREATE_UNICODE_ENVIRONMENT, pwszzEnvironment, NULL /*pwsz*/,
3467 &StartupInfo, &ProcInfo))
3468 ON(fatal, NILF, _("MkWinChildReExecMake: CreateProcessW failed: %u\n"), GetLastError());
3469 CloseHandle(ProcInfo.hThread);
3470
3471 /*
3472 * Wait for it to complete and forward the status code to our parent.
3473 */
3474 for (;;)
3475 {
3476 DWORD dwExitCode = -2222;
3477 DWORD dwStatus = WaitForSingleObject(ProcInfo.hProcess, INFINITE);
3478 if ( dwStatus == WAIT_IO_COMPLETION
3479 || dwStatus == WAIT_TIMEOUT /* whatever */)
3480 continue; /* however unlikely, these aren't fatal. */
3481
3482 /* Get the status code and terminate. */
3483 if (dwStatus == WAIT_OBJECT_0)
3484 {
3485 if (!GetExitCodeProcess(ProcInfo.hProcess, &dwExitCode))
3486 {
3487 ON(fatal, NILF, _("MkWinChildReExecMake: GetExitCodeProcess failed: %u\n"), GetLastError());
3488 dwExitCode = -2222;
3489 }
3490 }
3491 else if (dwStatus)
3492 dwExitCode = dwStatus;
3493
3494 CloseHandle(ProcInfo.hProcess);
3495 for (;;)
3496 exit(dwExitCode);
3497 }
3498}
3499
3500#ifdef WITH_RW_LOCK
3501/** Serialization with kmkbuiltin_redirect. */
3502void MkWinChildExclusiveAcquire(void)
3503{
3504 AcquireSRWLockExclusive(&g_RWLock);
3505}
3506
3507/** Serialization with kmkbuiltin_redirect. */
3508void MkWinChildExclusiveRelease(void)
3509{
3510 ReleaseSRWLockExclusive(&g_RWLock);
3511}
3512#endif /* WITH_RW_LOCK */
3513
3514/**
3515 * Implementation of the CLOSE_ON_EXEC macro.
3516 *
3517 * @returns errno value.
3518 * @param fd The file descriptor to hide from children.
3519 */
3520int MkWinChildUnrelatedCloseOnExec(int fd)
3521{
3522 if (fd >= 0)
3523 {
3524 HANDLE hNative = (HANDLE)_get_osfhandle(fd);
3525 if (hNative != INVALID_HANDLE_VALUE && hNative != NULL)
3526 {
3527 if (SetHandleInformation(hNative, HANDLE_FLAG_INHERIT /*clear*/ , 0 /*set*/))
3528 return 0;
3529 }
3530 return errno;
3531 }
3532 return EINVAL;
3533}
3534
Note: See TracBrowser for help on using the repository browser.