VirtualBox

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

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

kmk/winchildren: Dont use fprintf for error handling, but fatal, output_write_text, and line buffered fwrite(stderr). Fixed incorrect SetThreadGroupAffinity calls (set affinity mask to 0 rather than max).

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

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