VirtualBox

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

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

kmk/winchildren: Finally squashed the intermixed char-by-char output from MS tools by catching all normal child output and funnel it thru pipes. Except kmk_redirect, that is.

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

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