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

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

kmk/win: Suppress annoying the source filename output from CL.EXE. Fixed bug in mkWinChildcareWorkerFlushUnwritten.

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