VirtualBox

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

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

kmk/output: Memory buffering fixes.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 110.1 KB
Line 
1/* $Id: winchildren.c 3191 2018-03-26 09:16:06Z bird $ */
2/** @file
3 * Child process creation and management for kmk.
4 */
5
6/*
7 * Copyright (c) 2018 knut st. osmundsen <[email protected]>
8 *
9 * This file is part of kBuild.
10 *
11 * kBuild is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 3 of the License, or
14 * (at your option) any later version.
15 *
16 * kBuild is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with kBuild. If not, see <http://www.gnu.org/licenses/>
23 *
24 */
25
26/* No GNU coding style here atm, convert if upstreamed. */
27
28/** @page pg_win_children Windows child process creation and managment
29 *
30 * This new implementation aims at addressing the following:
31 *
32 * 1. Speed up process creation by doing the expensive CreateProcess call
33 * in a worker thread.
34 *
35 * 2. No 64 process limit imposed by WaitForMultipleObjects.
36 *
37 * 3. Better distribute jobs among processor groups.
38 *
39 * 4. Offloading more expensive kmkbuiltin operations to worker threads,
40 * making the main thread focus on managing child processes.
41 *
42 * 5. Output synchronization using reusable pipes.
43 *
44 *
45 * To be quite honest, the first item (CreateProcess expense) didn't occur to me
46 * at first and was more of a sideeffect discovered along the way. A test
47 * rebuilding IPRT went from 4m52s to 3m19s on a 8 thread system.
48 *
49 * The 2nd and 3rd goals are related to newer build servers that have lots of
50 * CPU threads and various Windows NT (aka NT OS/2 at the time) design choices
51 * made in the late 1980ies.
52 *
53 * WaitForMultipleObjects does not support waiting for more than 64 objects,
54 * unlike poll and select. This is just something everyone ends up having to
55 * work around in the end.
56 *
57 * Affinity masks are uintptr_t sized, so 64-bit hosts can only manage 64
58 * processors and 32-bit only 32. Workaround was introduced with Windows 7
59 * (IIRC) and is called processor groups. The CPU threads are grouped into 1 or
60 * more groups of up to 64 processors. Processes are generally scheduled to a
61 * signle processor group at first, but threads may be changed to be scheduled
62 * on different groups. This code will try distribute children evenly among the
63 * processor groups, using a very simple algorithm (see details in code).
64 *
65 *
66 * @section sec_win_children_av Remarks on Microsoft Defender and other AV
67 *
68 * Part of the motivation for writing this code was horrible CPU utilization on
69 * a brand new AMD Threadripper 1950X system with lots of memory and SSDs,
70 * running 64-bit Windows 10 build 16299.
71 *
72 * Turns out Microsoft defender adds some overhead to CreateProcess
73 * and other stuff:
74 * - Old make with CreateProcess on main thread:
75 * - With runtime defender enabled: 14 min 6 seconds
76 * - With runtime defender disabled: 4 min 49 seconds
77 * - New make with CreateProcess on worker thread (this code):
78 * - With runtime defender enabled: 6 min 29 seconds
79 * - With runtime defender disabled: 4 min 36 seconds
80 * - With runtime defender disabled out dir only: 5 min 59 seconds
81 *
82 * See also kWorker / kSubmit for more bickering about AV & disk encryption.
83 */
84
85
86/*********************************************************************************************************************************
87* Header Files *
88*********************************************************************************************************************************/
89#include "../makeint.h"
90#include "../job.h"
91#include "../filedef.h"
92#include "../debug.h"
93#include "../kmkbuiltin.h"
94#include "winchildren.h"
95
96#include <Windows.h>
97#include <Winternl.h>
98#include <assert.h>
99#include <process.h>
100#include <intrin.h>
101
102#include "nt/nt_child_inject_standard_handles.h"
103
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 DWORD cbUnwritten = pPipe->cbWritten - pPipe->offPendingRead;
640 if (cbUnwritten)
641 {
642#ifdef CONFIG_WITH_OUTPUT_IN_MEMORY
643 if (pChild->pMkChild)
644 {
645 output_write_bin(&pChild->pMkChild->output, pPipe->iWhich == 2, &pPipe->pbBuffer[pPipe->cbWritten], cbUnwritten);
646 pPipe->cbWritten += cbUnwritten;
647 }
648 else
649#endif
650 {
651 DWORD cbWritten = 0;
652 if (WriteFile(GetStdHandle(pPipe->iWhich == 1 ? STD_OUTPUT_HANDLE : STD_ERROR_HANDLE),
653 &pPipe->pbBuffer[pPipe->cbWritten], cbUnwritten, &cbWritten, NULL))
654 pPipe->cbWritten += cbWritten <= cbUnwritten ? cbWritten : cbUnwritten; /* paranoia */
655 }
656 }
657}
658
659/**
660 * Adds output to the given standard output for the child.
661 *
662 * There is no pending read when this function is called, so we're free to
663 * reshuffle the buffer if desirable.
664 *
665 * @param pChild The child.
666 * @param iWhich Which standard descriptor number.
667 * @param cbNewData How much more output was caught.
668 */
669static void mkWinChildcareWorkerCaughtMoreOutput(PWINCHILD pChild, PWINCCWPIPE pPipe, DWORD cbNewData)
670{
671 DWORD offStart = pPipe->cbWritten;
672 assert(offStart <= pPipe->offPendingRead);
673 if (cbNewData > 0)
674 {
675 DWORD offRest;
676
677 /* Move offPendingRead ahead by cbRead. */
678 pPipe->offPendingRead += cbNewData;
679 assert(pPipe->offPendingRead <= pPipe->cbBuffer);
680 if (pPipe->offPendingRead > pPipe->cbBuffer)
681 pPipe->offPendingRead = pPipe->cbBuffer;
682
683 /* Locate the last newline in the buffer. */
684 offRest = pPipe->offPendingRead;
685 while (offRest > offStart && pPipe->pbBuffer[offRest - 1] != '\n')
686 offRest--;
687
688 /* If none were found and we've less than 16 bytes left in the buffer, try
689 find a word boundrary to flush on instead. */
690 if ( offRest > offStart
691 || pPipe->cbBuffer - pPipe->offPendingRead + offStart > 16)
692 { /* likely */ }
693 else
694 {
695 offRest = pPipe->offPendingRead;
696 while ( offRest > offStart
697 && isalnum(pPipe->pbBuffer[offRest - 1]))
698 offRest--;
699 if (offRest == offStart)
700 offRest = pPipe->offPendingRead;
701 }
702 if (offRest > offStart)
703 {
704 /* Write out offStart..offRest. */
705 DWORD cbToWrite = offRest - offStart;
706#ifdef CONFIG_WITH_OUTPUT_IN_MEMORY
707 if (pChild->pMkChild)
708 {
709 output_write_bin(&pChild->pMkChild->output, pPipe->iWhich == 2, &pPipe->pbBuffer[offStart], cbToWrite);
710 offStart += cbToWrite;
711 pPipe->cbWritten = offStart;
712 }
713 else
714#endif
715 {
716 DWORD cbWritten = 0;
717 if (WriteFile(GetStdHandle(pPipe->iWhich == 1 ? STD_OUTPUT_HANDLE : STD_ERROR_HANDLE),
718 &pPipe->pbBuffer[offStart], cbToWrite, &cbWritten, NULL))
719 {
720 offStart += cbWritten <= cbToWrite ? cbWritten : cbToWrite; /* paranoia */
721 pPipe->cbWritten = offStart;
722 }
723 }
724 }
725 }
726
727 /* Shuffle the data to the front of the buffer. */
728 if (offStart > 0)
729 {
730 DWORD cbUnwritten = pPipe->offPendingRead - offStart;
731 if (cbUnwritten > 0)
732 memmove(pPipe->pbBuffer, &pPipe->pbBuffer[offStart], cbUnwritten);
733 pPipe->offPendingRead -= pPipe->cbWritten;
734 pPipe->cbWritten = 0;
735 }
736}
737
738/**
739 * Catches output from the given pipe.
740 *
741 * @param pChild The child.
742 * @param pPipe The pipe.
743 * @param fFlushing Set if we're flushing the pipe after the process
744 * terminated.
745 */
746static void mkWinChildcareWorkerCatchOutput(PWINCHILD pChild, PWINCCWPIPE pPipe, BOOL fFlushing)
747{
748 /*
749 * Deal with already pending read.
750 */
751 if (pPipe->fReadPending)
752 {
753 DWORD cbRead = 0;
754 if (GetOverlappedResult(pPipe->hPipeMine, &pPipe->Overlapped, &cbRead, !fFlushing))
755 {
756 mkWinChildcareWorkerCaughtMoreOutput(pChild, pPipe, cbRead);
757 pPipe->fReadPending = FALSE;
758 }
759 else if (fFlushing && GetLastError() == ERROR_IO_INCOMPLETE)
760 {
761 if (pPipe->offPendingRead > pPipe->cbWritten)
762 mkWinChildcareWorkerFlushUnwritten(pChild, pPipe);
763 return;
764 }
765 else
766 {
767 fprintf(stderr, "warning: GetOverlappedResult failed: %u\n", GetLastError());
768 pPipe->fReadPending = FALSE;
769 if (fFlushing)
770 return;
771 }
772 }
773
774 /*
775 * Read data till one becomes pending.
776 */
777 for (;;)
778 {
779 DWORD cbRead;
780
781 memset(&pPipe->Overlapped, 0, sizeof(pPipe->Overlapped));
782 pPipe->Overlapped.hEvent = pPipe->hEvent;
783 ResetEvent(pPipe->hEvent);
784
785 assert(pPipe->offPendingRead < pPipe->cbBuffer);
786 SetLastError(0);
787 cbRead = 0;
788 if (!ReadFile(pPipe->hPipeMine, &pPipe->pbBuffer[pPipe->offPendingRead],
789 pPipe->cbBuffer - pPipe->offPendingRead, &cbRead, &pPipe->Overlapped))
790 {
791 DWORD dwErr = GetLastError();
792 if (dwErr == ERROR_IO_PENDING)
793 pPipe->fReadPending = TRUE;
794 else
795 fprintf(stderr, "warning: ReadFile failed on standard %s: %u\n",
796 pPipe->iWhich == 1 ? "output" : "error", GetLastError());
797 return;
798 }
799
800 mkWinChildcareWorkerCaughtMoreOutput(pChild, pPipe, cbRead);
801 }
802}
803
804/**
805 * Commmon worker for waiting on a child process and retrieving the exit code.
806 *
807 * @param pWorker The worker.
808 * @param pChild The child.
809 * @param hProcess The process handle.
810 * @param pwszJob The job name.
811 * @param fCatchOutput Set if we need to work the output pipes
812 * associated with the worker.
813 */
814static void mkWinChildcareWorkerWaitForProcess(PWINCHILDCAREWORKER pWorker, PWINCHILD pChild, HANDLE hProcess,
815 WCHAR const *pwszJob, BOOL fCatchOutput)
816{
817 DWORD const msStart = GetTickCount();
818 DWORD msNextMsg = msStart + 15000;
819 for (;;)
820 {
821 /*
822 * Do the waiting and output catching.
823 */
824 DWORD dwStatus;
825 if (!fCatchOutput)
826 dwStatus = WaitForSingleObject(hProcess, 15001 /*ms*/);
827 else
828 {
829 HANDLE ahHandles[3] = { hProcess, pWorker->StdOut.hEvent, pWorker->StdErr.hEvent };
830 dwStatus = WaitForMultipleObjects(3, ahHandles, FALSE /*fWaitAll*/, 1000 /*ms*/);
831 if (dwStatus == WAIT_OBJECT_0 + 1)
832 mkWinChildcareWorkerCatchOutput(pChild, &pWorker->StdOut, FALSE /*fFlushing*/);
833 else if (dwStatus == WAIT_OBJECT_0 + 2)
834 mkWinChildcareWorkerCatchOutput(pChild, &pWorker->StdErr, FALSE /*fFlushing*/);
835 }
836 assert(dwStatus != WAIT_FAILED);
837
838 /*
839 * Get the exit code and return if the process was signalled as done.
840 */
841 if (dwStatus == WAIT_OBJECT_0)
842 {
843 DWORD dwExitCode = -42;
844 if (GetExitCodeProcess(hProcess, &dwExitCode))
845 {
846 pChild->iExitCode = (int)dwExitCode;
847 if (!fCatchOutput)
848 {
849 mkWinChildcareWorkerCatchOutput(pChild, &pWorker->StdOut, TRUE /*fFlushing*/);
850 mkWinChildcareWorkerCatchOutput(pChild, &pWorker->StdErr, TRUE /*fFlushing*/);
851 }
852 return;
853 }
854 }
855 /*
856 * Loop again if just a timeout or pending output?
857 * Put out a message every 15 or 30 seconds if the job takes a while.
858 */
859 else if ( dwStatus == WAIT_TIMEOUT
860 || dwStatus == WAIT_OBJECT_0 + 1
861 || dwStatus == WAIT_OBJECT_0 + 2
862 || dwStatus == WAIT_IO_COMPLETION)
863 {
864 DWORD msNow = GetTickCount();
865 if (msNow >= msNextMsg)
866 {
867 if ( !pChild->pMkChild
868 || !pChild->pMkChild->recursive) /* ignore make recursions */
869 {
870 if ( !pChild->pMkChild
871 || !pChild->pMkChild->file
872 || !pChild->pMkChild->file->name)
873 printf("Pid %u ('%s') still running after %u seconds\n",
874 GetProcessId(hProcess), pwszJob, (msNow - msStart) / 1000);
875 else
876 printf("Target '%s' (pid %u) still running after %u seconds\n",
877 pChild->pMkChild->file->name, GetProcessId(hProcess), (msNow - msStart) / 1000);
878 }
879
880 /* After 15s, 30s, 60s, 120s, 180s, ... */
881 if (msNextMsg == msStart + 15000)
882 msNextMsg += 15000;
883 else
884 msNextMsg += 30000;
885 }
886 continue;
887 }
888
889 /* Something failed. */
890 pChild->iExitCode = GetLastError();
891 if (pChild->iExitCode == 0)
892 pChild->iExitCode = -4242;
893 return;
894 }
895}
896
897
898/**
899 * Closes standard handles that need closing before destruction.
900 *
901 * @param pChild The child (WINCHILDTYPE_PROCESS).
902 */
903static void mkWinChildcareWorkerCloseStandardHandles(PWINCHILD pChild)
904{
905 if ( pChild->u.Process.fCloseStdOut
906 && pChild->u.Process.hStdOut != INVALID_HANDLE_VALUE)
907 {
908 CloseHandle(pChild->u.Process.hStdOut);
909 pChild->u.Process.hStdOut = INVALID_HANDLE_VALUE;
910 pChild->u.Process.fCloseStdOut = FALSE;
911 }
912 if ( pChild->u.Process.fCloseStdErr
913 && pChild->u.Process.hStdErr != INVALID_HANDLE_VALUE)
914 {
915 CloseHandle(pChild->u.Process.hStdErr);
916 pChild->u.Process.hStdErr = INVALID_HANDLE_VALUE;
917 pChild->u.Process.fCloseStdErr = FALSE;
918 }
919}
920
921
922/**
923 * Does the actual process creation given.
924 *
925 * @returns 0 if there is anything to wait on, otherwise non-zero windows error.
926 * @param pWorker The childcare worker.
927 * @param pChild The child.
928 * @param pwszImageName The image path.
929 * @param pwszCommandLine The command line.
930 * @param pwszzEnvironment The enviornment block.
931 */
932static int mkWinChildcareWorkerCreateProcess(PWINCHILDCAREWORKER pWorker, PWINCHILD pChild, WCHAR const *pwszImageName,
933 WCHAR const *pwszCommandLine, WCHAR const *pwszzEnvironment)
934{
935 PROCESS_INFORMATION ProcInfo;
936 STARTUPINFOW StartupInfo;
937 DWORD fFlags = CREATE_UNICODE_ENVIRONMENT;
938 BOOL const fHaveHandles = pChild->u.Process.hStdErr != INVALID_HANDLE_VALUE
939 || pChild->u.Process.hStdOut != INVALID_HANDLE_VALUE;
940 BOOL fRet;
941 DWORD dwErr;
942#ifdef KMK
943 extern int process_priority;
944#endif
945
946 /*
947 * Populate startup info.
948 *
949 * Turns out we can get away without passing TRUE for the inherit handles
950 * parameter to CreateProcess when we're not using STARTF_USESTDHANDLES.
951 * At least on NT, which is all worth caring about at this point + context IMO.
952 *
953 * Not inherting the handles is a good thing because it means we won't
954 * accidentally end up with a pipe handle or such intended for a different
955 * child process, potentially causing the EOF/HUP event to be delayed.
956 *
957 * Since the present handle inhertiance requirements only involves standard
958 * output and error, we'll never set the inherit handles flag and instead
959 * do manual handle duplication and planting.
960 */
961 memset(&StartupInfo, 0, sizeof(StartupInfo));
962 StartupInfo.cb = sizeof(StartupInfo);
963 GetStartupInfoW(&StartupInfo);
964 StartupInfo.lpReserved2 = 0; /* No CRT file handle + descriptor info possible, sorry. */
965 StartupInfo.cbReserved2 = 0;
966 if ( !fHaveHandles
967 && !pChild->u.Process.fCatchOutput)
968 StartupInfo.dwFlags &= ~STARTF_USESTDHANDLES;
969 else
970 {
971 fFlags |= CREATE_SUSPENDED;
972 StartupInfo.dwFlags &= ~STARTF_USESTDHANDLES;
973 }
974
975 /*
976 * Flags.
977 */
978#ifdef KMK
979 switch (process_priority)
980 {
981 case 1: fFlags |= CREATE_SUSPENDED | IDLE_PRIORITY_CLASS; break;
982 case 2: fFlags |= CREATE_SUSPENDED | BELOW_NORMAL_PRIORITY_CLASS; break;
983 case 3: fFlags |= CREATE_SUSPENDED | NORMAL_PRIORITY_CLASS; break;
984 case 4: fFlags |= CREATE_SUSPENDED | HIGH_PRIORITY_CLASS; break;
985 case 5: fFlags |= CREATE_SUSPENDED | REALTIME_PRIORITY_CLASS; break;
986 }
987#endif
988 if (g_cProcessorGroups > 1)
989 fFlags |= CREATE_SUSPENDED;
990
991 /*
992 * Try create the process.
993 */
994 DB(DB_JOBS, ("CreateProcessW(%ls, %ls,,, TRUE, %#x...)\n", pwszImageName, pwszCommandLine, fFlags));
995 memset(&ProcInfo, 0, sizeof(ProcInfo));
996#ifdef WITH_RW_LOCK
997 AcquireSRWLockShared(&g_RWLock);
998#endif
999
1000 fRet = CreateProcessW((WCHAR *)pwszImageName, (WCHAR *)pwszCommandLine, NULL /*pProcSecAttr*/, NULL /*pThreadSecAttr*/,
1001 FALSE /*fInheritHandles*/, fFlags, (WCHAR *)pwszzEnvironment, NULL /*pwsz*/, &StartupInfo, &ProcInfo);
1002 dwErr = GetLastError();
1003
1004#ifdef WITH_RW_LOCK
1005 ReleaseSRWLockShared(&g_RWLock);
1006#endif
1007 if (fRet)
1008 pChild->u.Process.hProcess = ProcInfo.hProcess;
1009 else
1010 {
1011 fprintf(stderr, "CreateProcess(%ls) failed: %u\n", pwszImageName, dwErr);
1012 return pChild->iExitCode = (int)dwErr;
1013 }
1014
1015 /*
1016 * If the child is suspended, we've got some adjustment work to be done.
1017 */
1018 dwErr = ERROR_SUCCESS;
1019 if (fFlags & CREATE_SUSPENDED)
1020 {
1021 /*
1022 * First do handle inhertiance as that's the most complicated.
1023 */
1024 if (fHaveHandles || pChild->u.Process.fCatchOutput)
1025 {
1026 char szErrMsg[128];
1027 BOOL afReplace[3];
1028 HANDLE ahChild[3];
1029
1030 afReplace[0] = FALSE;
1031 ahChild[0] = INVALID_HANDLE_VALUE;
1032 if (fHaveHandles)
1033 {
1034 afReplace[1] = pChild->u.Process.hStdOut != INVALID_HANDLE_VALUE;
1035 ahChild[1] = pChild->u.Process.hStdOut;
1036 afReplace[2] = pChild->u.Process.hStdErr != INVALID_HANDLE_VALUE;
1037 ahChild[2] = pChild->u.Process.hStdErr;
1038 }
1039 else
1040 {
1041 afReplace[1] = TRUE;
1042 ahChild[1] = pWorker->StdOut.hPipeChild;
1043 afReplace[2] = TRUE;
1044 ahChild[2] = pWorker->StdErr.hPipeChild;
1045 }
1046
1047 dwErr = nt_child_inject_standard_handles(ProcInfo.hProcess, afReplace, ahChild, szErrMsg, sizeof(szErrMsg));
1048 if (dwErr != 0)
1049 fprintf(stderr, "%s\n", szErrMsg);
1050 }
1051
1052 /*
1053 * Assign processor group (ignore failure).
1054 */
1055 if (g_cProcessorGroups > 1)
1056 {
1057 GROUP_AFFINITY Affinity = { ~(ULONG_PTR)0, pWorker->iProcessorGroup, { 0, 0, 0 } };
1058 fRet = g_pfnSetThreadGroupAffinity(ProcInfo.hThread, &Affinity, NULL);
1059 assert(fRet);
1060 }
1061
1062#ifdef KMK
1063 /*
1064 * Set priority (ignore failure).
1065 */
1066 switch (process_priority)
1067 {
1068 case 1: fRet = SetThreadPriority(ProcInfo.hThread, THREAD_PRIORITY_IDLE); break;
1069 case 2: fRet = SetThreadPriority(ProcInfo.hThread, THREAD_PRIORITY_BELOW_NORMAL); break;
1070 case 3: fRet = SetThreadPriority(ProcInfo.hThread, THREAD_PRIORITY_NORMAL); break;
1071 case 4: fRet = SetThreadPriority(ProcInfo.hThread, THREAD_PRIORITY_HIGHEST); break;
1072 case 5: fRet = SetThreadPriority(ProcInfo.hThread, THREAD_PRIORITY_TIME_CRITICAL); break;
1073 default: fRet = TRUE;
1074 }
1075 assert(fRet);
1076#endif
1077
1078 /*
1079 * Resume the thread if the adjustments succeeded, otherwise kill it.
1080 */
1081 if (dwErr == ERROR_SUCCESS)
1082 {
1083 fRet = ResumeThread(ProcInfo.hThread);
1084 assert(fRet);
1085 if (!fRet)
1086 {
1087 dwErr = GetLastError();
1088 fprintf(stderr, "ResumeThread failed on child process: %u\n", dwErr);
1089 }
1090 }
1091 if (dwErr != ERROR_SUCCESS)
1092 TerminateProcess(ProcInfo.hProcess, dwErr);
1093 }
1094
1095 /*
1096 * Close unnecessary handles.
1097 */
1098 mkWinChildcareWorkerCloseStandardHandles(pChild);
1099 CloseHandle(ProcInfo.hThread);
1100 return 0;
1101}
1102
1103
1104#define MKWCCWCMD_F_CYGWIN_SHELL 1
1105#define MKWCCWCMD_F_MKS_SHELL 2
1106#define MKWCCWCMD_F_HAVE_SH 4
1107#define MKWCCWCMD_F_HAVE_KASH_C 8 /**< kmk_ash -c "..." */
1108
1109static int mkWinChildcareWorkerConvertCommandline(char **papszArgs, unsigned fFlags, WCHAR **ppwszCommandLine)
1110{
1111 struct ARGINFO
1112 {
1113 size_t cchSrc;
1114 size_t cwcDst; /**< converted size w/o terminator. */
1115 size_t cwcDstExtra : 24; /**< Only set with fSlowly. */
1116 size_t fSlowly : 1;
1117 size_t fQuoteIt : 1;
1118 size_t fEndSlashes : 1; /**< if escapes needed for trailing backslashes. */
1119 size_t fExtraSpace : 1; /**< if kash -c "" needs an extra space before the quote. */
1120 } *paArgInfo;
1121 size_t cArgs;
1122 size_t i;
1123 size_t cwcNeeded;
1124 WCHAR *pwszDst;
1125 WCHAR *pwszCmdLine;
1126
1127 /*
1128 * Count them first so we can allocate an info array of the stack.
1129 */
1130 cArgs = 0;
1131 while (papszArgs[cArgs] != NULL)
1132 cArgs++;
1133 paArgInfo = (struct ARGINFO *)alloca(sizeof(paArgInfo[0]) * cArgs);
1134
1135 /*
1136 * Preprocess them and calculate the exact command line length.
1137 */
1138 cwcNeeded = 1;
1139 for (i = 0; i < cArgs; i++)
1140 {
1141 char *pszSrc = papszArgs[i];
1142 size_t cchSrc = strlen(pszSrc);
1143 paArgInfo[i].cchSrc = cchSrc;
1144 if (cchSrc == 0)
1145 {
1146 /* empty needs quoting. */
1147 paArgInfo[i].cwcDst = 2;
1148 paArgInfo[i].cwcDstExtra = 0;
1149 paArgInfo[i].fSlowly = 0;
1150 paArgInfo[i].fQuoteIt = 1;
1151 paArgInfo[i].fExtraSpace = 0;
1152 paArgInfo[i].fEndSlashes = 0;
1153 }
1154 else
1155 {
1156 const char *pszSpace = memchr(pszSrc, ' ', cchSrc);
1157 const char *pszTab = memchr(pszSrc, '\t', cchSrc);
1158 const char *pszDQuote = memchr(pszSrc, '"', cchSrc);
1159 const char *pszEscape = memchr(pszSrc, '\\', cchSrc);
1160 int cwcDst = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszSrc, cchSrc + 1, NULL, 0);
1161 if (cwcDst >= 0)
1162 --cwcDst;
1163 else
1164 {
1165 DWORD dwErr = GetLastError();
1166 fprintf(stderr, _("MultiByteToWideChar failed to convert argv[%u] (%s): %u\n"), i, pszSrc, dwErr);
1167 return dwErr;
1168 }
1169#if 0
1170 if (!pszSpace && !pszTab && !pszDQuote && !pszEscape)
1171 {
1172 /* no special handling needed. */
1173 paArgInfo[i].cwcDst = cwcDst;
1174 paArgInfo[i].cwcDstExtra = 0;
1175 paArgInfo[i].fSlowly = 0;
1176 paArgInfo[i].fQuoteIt = 0;
1177 paArgInfo[i].fExtraSpace = 0;
1178 paArgInfo[i].fEndSlashes = 0;
1179 }
1180 else if (!pszDQuote && !pszEscape)
1181 {
1182 /* Just double quote it. */
1183 paArgInfo[i].cwcDst = cwcDst + 2;
1184 paArgInfo[i].cwcDstExtra = 0;
1185 paArgInfo[i].fSlowly = 0;
1186 paArgInfo[i].fQuoteIt = 1;
1187 paArgInfo[i].fExtraSpace = 0;
1188 paArgInfo[i].fEndSlashes = 0;
1189 }
1190 else
1191#endif
1192 {
1193 /* Complicated, need to scan the string to figure out what to do. */
1194 size_t cwcDstExtra;
1195 int cBackslashes;
1196 char ch;
1197
1198 paArgInfo[i].fQuoteIt = 0;
1199 paArgInfo[i].fSlowly = 1;
1200 paArgInfo[i].fExtraSpace = 0;
1201 paArgInfo[i].fEndSlashes = 0;
1202
1203 cwcDstExtra = 0;
1204 cBackslashes = 0;
1205 while ((ch = *pszSrc++) != '\0')
1206 {
1207 switch (ch)
1208 {
1209 default:
1210 cBackslashes = 0;
1211 break;
1212
1213 case '\\':
1214 cBackslashes++;
1215 break;
1216
1217 case '"':
1218 if (fFlags & (MKWCCWCMD_F_CYGWIN_SHELL | MKWCCWCMD_F_MKS_SHELL))
1219 cwcDstExtra += 1;
1220 else
1221 cwcDstExtra += 1 + cBackslashes;
1222 break;
1223
1224 case ' ':
1225 case '\t':
1226 if (!paArgInfo[i].fQuoteIt)
1227 {
1228 paArgInfo[i].fQuoteIt = 1;
1229 cwcDstExtra += 2;
1230 }
1231 cBackslashes = 0;
1232 break;
1233 }
1234 }
1235
1236 if ( cBackslashes > 0
1237 && paArgInfo[i].fQuoteIt
1238 && !(fFlags & (MKWCCWCMD_F_CYGWIN_SHELL | MKWCCWCMD_F_MKS_SHELL)))
1239 {
1240 cwcDstExtra += cBackslashes;
1241 paArgInfo[i].fEndSlashes = 1;
1242 }
1243
1244 paArgInfo[i].cwcDst = cwcDst + cwcDstExtra;
1245 paArgInfo[i].cwcDstExtra = cwcDstExtra;
1246 }
1247 }
1248
1249 if ( (fFlags & MKWCCWCMD_F_HAVE_KASH_C)
1250 && paArgInfo[i].fQuoteIt)
1251 {
1252 paArgInfo[i].fExtraSpace = 1;
1253 paArgInfo[i].cwcDst++;
1254 paArgInfo[i].cwcDstExtra++;
1255 }
1256
1257 cwcNeeded += (i != 0) + paArgInfo[i].cwcDst;
1258 }
1259
1260 /*
1261 * Allocate the result buffer and do the actual conversion.
1262 */
1263 pwszDst = pwszCmdLine = (WCHAR *)xmalloc(sizeof(WCHAR) * cwcNeeded);
1264 for (i = 0; i < cArgs; i++)
1265 {
1266 char *pszSrc = papszArgs[i];
1267 size_t cwcDst = paArgInfo[i].cwcDst;
1268
1269 if (i != 0)
1270 *pwszDst++ = L' ';
1271
1272 if (paArgInfo[i].fQuoteIt)
1273 {
1274 *pwszDst++ = L'"';
1275 cwcDst -= 2;
1276 }
1277
1278 if (!paArgInfo[i].fSlowly)
1279 {
1280 int cwcDst2 = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszSrc, paArgInfo[i].cchSrc, pwszDst, cwcDst + 1);
1281 assert(cwcDst2 >= 0);
1282 pwszDst += cwcDst;
1283 }
1284 else
1285 {
1286 /* Do the conversion into the end of the output buffer, then move
1287 it up to where it should be char by char. */
1288 size_t cBackslashes;
1289 size_t cwcLeft = paArgInfo[i].cwcDst - paArgInfo[i].cwcDstExtra;
1290 WCHAR volatile *pwchSlowSrc = pwszDst + paArgInfo[i].cwcDstExtra;
1291 WCHAR volatile *pwchSlowDst = pwszDst;
1292 int cwcDst2 = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszSrc, paArgInfo[i].cchSrc,
1293 (WCHAR *)pwchSlowSrc, cwcLeft + 1);
1294 assert(cwcDst2 >= 0);
1295
1296 cBackslashes = 0;
1297 while (cwcLeft-- > 0)
1298 {
1299 WCHAR wcSrc = *pwchSlowSrc++;
1300 if (wcSrc != L'\\' && wcSrc != L'"')
1301 cBackslashes = 0;
1302 else if (wcSrc == L'\\')
1303 cBackslashes++;
1304 else if ( (fFlags & (MKWCCWCMD_F_CYGWIN_SHELL | MKWCCWCMD_F_HAVE_SH))
1305 == (MKWCCWCMD_F_CYGWIN_SHELL | MKWCCWCMD_F_HAVE_SH))
1306 *pwchSlowDst++ = L'"'; /* cygwin: '"' instead of '\\', no escaped slashes. */
1307 else
1308 {
1309 if (!(fFlags & (MKWCCWCMD_F_CYGWIN_SHELL | MKWCCWCMD_F_MKS_SHELL)))
1310 cBackslashes = 1;
1311 while (cBackslashes-- > 0)
1312 *pwchSlowDst++ = L'\\';
1313 }
1314 *pwchSlowDst++ = wcSrc;
1315 }
1316
1317 if (paArgInfo[i].fEndSlashes)
1318 while (cBackslashes-- > 0)
1319 *pwchSlowDst++ = L'\\';
1320
1321 pwszDst += cwcDst;
1322 assert(pwszDst == (WCHAR *)pwchSlowDst);
1323 }
1324
1325 if (paArgInfo[i].fExtraSpace)
1326 *pwszDst++ = L' ';
1327 if (paArgInfo[i].fQuoteIt)
1328 *pwszDst++ = L'"';
1329 }
1330 *pwszDst = L'\0';
1331 *ppwszCommandLine = pwszCmdLine;
1332 return 0;
1333}
1334
1335static int mkWinChildcareWorkerConvertCommandlineWithShell(const WCHAR *pwszShell, char **papszArgs, WCHAR **ppwszCommandLine)
1336{
1337 fprintf(stderr, "%s: not found!\n", papszArgs[0]);
1338//__debugbreak();
1339 return ERROR_FILE_NOT_FOUND;
1340}
1341
1342/**
1343 * Searches the environment block for the PATH variable.
1344 *
1345 * @returns Pointer to the path in the block or "." in pwszPathFallback.
1346 * @param pwszzEnv The UTF-16 environment block to search.
1347 * @param pwszPathFallback Fallback.
1348 */
1349static const WCHAR *mkWinChildcareWorkerFindPathValue(const WCHAR *pwszzEnv, WCHAR pwszPathFallback[4])
1350{
1351 while (*pwszzEnv)
1352 {
1353 size_t cwcVar = wcslen(pwszzEnv);
1354 if (!IS_PATH_ENV_VAR(cwcVar, pwszzEnv))
1355 pwszzEnv += cwcVar + 1;
1356 else if (cwcVar > 5)
1357 return &pwszzEnv[5];
1358 else
1359 break;
1360 }
1361 pwszPathFallback[0] = L'.';
1362 pwszPathFallback[1] = L'\0';
1363 return pwszPathFallback;
1364}
1365
1366/**
1367 * Checks if we need to had this executable file to the shell.
1368 *
1369 * @returns TRUE if it's shell fooder, FALSE if we think windows can handle it.
1370 * @param hFile Handle to the file in question
1371 */
1372static BOOL mkWinChildcareWorkerCheckIfNeedShell(HANDLE hFile)
1373{
1374 /*
1375 * Read the first 512 bytes and check for an executable image header.
1376 */
1377 union
1378 {
1379 DWORD dwSignature;
1380 WORD wSignature;
1381 BYTE ab[128];
1382 } uBuf;
1383 DWORD cbRead;
1384 uBuf.dwSignature = 0;
1385 if ( ReadFile(hFile, &uBuf, sizeof(uBuf), &cbRead, NULL /*pOverlapped*/)
1386 && cbRead == sizeof(uBuf))
1387 {
1388 if (uBuf.wSignature == IMAGE_DOS_SIGNATURE)
1389 return FALSE;
1390 if (uBuf.dwSignature == IMAGE_NT_SIGNATURE)
1391 return FALSE;
1392 if ( uBuf.wSignature == IMAGE_OS2_SIGNATURE /* NE */
1393 || uBuf.wSignature == 0x5d4c /* LX */
1394 || uBuf.wSignature == IMAGE_OS2_SIGNATURE_LE /* LE */)
1395 return FALSE;
1396 }
1397 return TRUE;
1398}
1399
1400
1401/**
1402 * Tries to locate the image file, searching the path and maybe falling back on
1403 * the shell in case it knows more (think cygwin with its own view of the file
1404 * system).
1405 *
1406 * This will also check for shell script, falling back on the shell too to
1407 * handle those.
1408 *
1409 * @returns 0 on success, windows error code on failure.
1410 * @param pszArg0 The first argument.
1411 * @param pwszSearchPath In case mkWinChildcareWorkerConvertEnvironment
1412 * had a chance of locating the search path already.
1413 * @param pwszzEnv The environment block, in case we need to look for
1414 * the path.
1415 * @param pszShell The shell.
1416 * @param ppwszImagePath Where to return the pointer to the image path. This
1417 * could be the shell.
1418 * @param pfNeedShell Where to return shell vs direct execution indicator.
1419 */
1420static int mkWinChildcareWorkerFindImage(char const *pszArg0, WCHAR *pwszSearchPath, WCHAR const *pwszzEnv,
1421 const char *pszShell, WCHAR **ppwszImagePath, BOOL *pfNeedShell)
1422{
1423 /** @todo Slap a cache on this code. We usually end up executing the same
1424 * stuff over and over again (e.g. compilers, linkers, etc).
1425 * Hitting the file system is slow on windows. */
1426
1427 /*
1428 * Convert pszArg0 to unicode so we can work directly on that.
1429 */
1430 WCHAR wszArg0[MKWINCHILD_MAX_PATH + 4]; /* +4 for painless '.exe' appending */
1431 DWORD dwErr;
1432 size_t cbArg0 = strlen(pszArg0) + 1;
1433 int const cwcArg0 = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszArg0, cbArg0, wszArg0, MKWINCHILD_MAX_PATH);
1434 if (cwcArg0 > 0)
1435 {
1436 HANDLE hFile = INVALID_HANDLE_VALUE;
1437 WCHAR wszPathBuf[MKWINCHILD_MAX_PATH + 4]; /* +4 for painless '.exe' appending */
1438 int cwc;
1439
1440 /*
1441 * If there isn't an .exe suffix, we may have to add one.
1442 * Also we ASSUME that .exe suffixes means no hash bang detection needed.
1443 */
1444 int const fHasExeSuffix = cwcArg0 > CSTRLEN(".exe")
1445 && wszArg0[cwcArg0 - 4] == '.'
1446 && (wszArg0[cwcArg0 - 3] == L'e' || wszArg0[cwcArg0 - 3] == L'E')
1447 && (wszArg0[cwcArg0 - 2] == L'x' || wszArg0[cwcArg0 - 2] == L'X')
1448 && (wszArg0[cwcArg0 - 1] == L'e' || wszArg0[cwcArg0 - 1] == L'E');
1449
1450 /*
1451 * If there isn't any path specified, we need to search the PATH env.var.
1452 */
1453 int const fHasPath = wszArg0[1] == L':'
1454 || wszArg0[0] == L'\\'
1455 || wszArg0[0] == L'/'
1456 || wmemchr(wszArg0, L'/', cwcArg0)
1457 || wmemchr(wszArg0, L'\\', cwcArg0);
1458
1459 /* Before we do anything, flip UNIX slashes to DOS ones. */
1460 WCHAR *pwc = wszArg0;
1461 while ((pwc = wcschr(pwc, L'/')) != NULL)
1462 *pwc++ = L'\\';
1463
1464 /* Don't need to set this all the time... */
1465 *pfNeedShell = FALSE;
1466
1467 /*
1468 * If any kind of path is specified in arg0, we will not search the
1469 * PATH env.var and can limit ourselves to maybe slapping a .exe on to it.
1470 */
1471 if (fHasPath)
1472 {
1473 /*
1474 * If relative to a CWD, turn it into an absolute one.
1475 */
1476 unsigned cwcPath = cwcArg0;
1477 WCHAR *pwszPath = wszArg0;
1478 if ( *pwszPath != L'\\'
1479 && (pwszPath[1] != ':' || pwszPath[2] != L'\\') )
1480 {
1481 DWORD cwcAbsPath = GetFullPathNameW(wszArg0, MKWINCHILD_MAX_PATH, wszPathBuf, NULL);
1482 if (cwcAbsPath > 0)
1483 {
1484 cwcPath = cwcAbsPath + 1; /* include terminator, like MultiByteToWideChar does. */
1485 pwszPath = wszPathBuf;
1486 }
1487 }
1488
1489 /*
1490 * Check with .exe suffix first.
1491 * We don't open .exe files and look for hash bang stuff, we just
1492 * assume they are executable images that CreateProcess can deal with.
1493 */
1494 if (!fHasExeSuffix)
1495 {
1496 pwszPath[cwcPath - 1] = L'.';
1497 pwszPath[cwcPath ] = L'e';
1498 pwszPath[cwcPath + 1] = L'x';
1499 pwszPath[cwcPath + 2] = L'e';
1500 pwszPath[cwcPath + 3] = L'\0';
1501 }
1502
1503#ifdef KMK
1504 if (utf16_regular_file_p(pwszPath))
1505#else
1506 if (GetFileAttributesW(pwszPath) != INVALID_FILE_ATTRIBUTES)
1507#endif
1508 return mkWinChildDuplicateUtf16String(pwszPath, cwcPath + 4, ppwszImagePath);
1509
1510 /*
1511 * If no suffix was specified, try without .exe too, but now we need
1512 * to see if it's for the shell or CreateProcess.
1513 */
1514 if (!fHasExeSuffix)
1515 {
1516 pwszPath[cwcPath - 1] = L'\0';
1517#ifdef KMK
1518 if (utf16_regular_file_p(pwszPath))
1519#endif
1520 {
1521 hFile = CreateFileW(pwszPath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
1522 NULL /*pSecAttr*/, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1523 if (hFile != INVALID_HANDLE_VALUE)
1524 {
1525 *pfNeedShell = mkWinChildcareWorkerCheckIfNeedShell(hFile);
1526 CloseHandle(hFile);
1527 if (!*pfNeedShell)
1528 return mkWinChildDuplicateUtf16String(pwszPath, cwcPath, ppwszImagePath);
1529 }
1530 }
1531 }
1532 }
1533 /*
1534 * No path, need to search the PATH env.var. for the executable, maybe
1535 * adding an .exe suffix while do so if that is missing.
1536 */
1537 else
1538 {
1539 BOOL fSearchedCwd = FALSE;
1540 WCHAR wszPathFallback[4];
1541 if (!pwszSearchPath)
1542 pwszSearchPath = (WCHAR *)mkWinChildcareWorkerFindPathValue(pwszzEnv, wszPathFallback);
1543
1544 for (;;)
1545 {
1546 size_t cwcCombined;
1547
1548 /*
1549 * Find the end of the current PATH component.
1550 */
1551 size_t cwcSkip;
1552 WCHAR wcEnd;
1553 size_t cwcComponent = 0;
1554 WCHAR wc;
1555 while ((wc = pwszSearchPath[cwcComponent]) != L'\0')
1556 {
1557 if (wc != ';' && wc != ':')
1558 { /* likely */ }
1559 else if (wc == ';')
1560 break;
1561 else if (cwcComponent != (pwszSearchPath[cwcComponent] != L'"' ? 1 : 2))
1562 break;
1563 cwcComponent++;
1564 }
1565 wcEnd = wc;
1566
1567 /* Trim leading spaces and double quotes. */
1568 while ( cwcComponent > 0
1569 && ((wc = *pwszSearchPath) == L'"' || wc == L' ' || wc == L'\t'))
1570 {
1571 pwszSearchPath++;
1572 cwcComponent--;
1573 }
1574 cwcSkip = cwcComponent;
1575
1576 /* Trim trailing spaces & double quotes. */
1577 while ( cwcComponent > 0
1578 && ((wc = pwszSearchPath[cwcComponent - 1]) == L'"' || wc == L' ' || wc == L'\t'))
1579 cwcComponent--;
1580
1581 /*
1582 * Skip empty components. Join the component and the filename, making sure to
1583 * resolve any CWD relative stuff first.
1584 */
1585 cwcCombined = cwcComponent + 1 + cwcArg0;
1586 if (cwcComponent > 0 && cwcCombined <= MKWINCHILD_MAX_PATH)
1587 {
1588#ifndef KMK
1589 DWORD dwAttribs;
1590#endif
1591
1592 /* Copy the component into wszPathBuf, maybe abspath'ing it. */
1593 DWORD cwcAbsPath = 0;
1594 if ( *pwszSearchPath != L'\\'
1595 && (pwszSearchPath[1] != ':' || pwszSearchPath[2] != L'\\') )
1596 {
1597 /* To save an extra buffer + copying, we'll temporarily modify the PATH
1598 value in our converted UTF-16 environment block. */
1599 WCHAR const wcSaved = pwszSearchPath[cwcComponent];
1600 pwszSearchPath[cwcComponent] = L'\0';
1601 cwcAbsPath = GetFullPathNameW(pwszSearchPath, MKWINCHILD_MAX_PATH, wszPathBuf, NULL);
1602 pwszSearchPath[cwcComponent] = wcSaved;
1603 if (cwcAbsPath > 0 && cwcAbsPath + 1 + cwcArg0 <= MKWINCHILD_MAX_PATH)
1604 cwcCombined = cwcAbsPath + 1 + cwcArg0;
1605 else
1606 cwcAbsPath = 0;
1607 }
1608 if (cwcAbsPath == 0)
1609 {
1610 memcpy(wszPathBuf, pwszSearchPath, cwcComponent * sizeof(WCHAR));
1611 cwcAbsPath = cwcComponent;
1612 }
1613
1614 /* Append the filename. */
1615 if ((wc = wszPathBuf[cwcAbsPath - 1]) == L'\\' || wc == L'/' || wc == L':')
1616 {
1617 memcpy(&wszPathBuf[cwcAbsPath], wszArg0, cwcArg0 * sizeof(WCHAR));
1618 cwcCombined--;
1619 }
1620 else
1621 {
1622 wszPathBuf[cwcAbsPath] = L'\\';
1623 memcpy(&wszPathBuf[cwcAbsPath + 1], wszArg0, cwcArg0 * sizeof(WCHAR));
1624 }
1625 assert(wszPathBuf[cwcCombined - 1] == L'\0');
1626
1627 /* DOS slash conversion */
1628 pwc = wszPathBuf;
1629 while ((pwc = wcschr(pwc, L'/')) != NULL)
1630 *pwc++ = L'\\';
1631
1632 /*
1633 * Search with exe suffix first.
1634 */
1635 if (!fHasExeSuffix)
1636 {
1637 wszPathBuf[cwcCombined - 1] = L'.';
1638 wszPathBuf[cwcCombined ] = L'e';
1639 wszPathBuf[cwcCombined + 1] = L'x';
1640 wszPathBuf[cwcCombined + 2] = L'e';
1641 wszPathBuf[cwcCombined + 3] = L'\0';
1642 }
1643#ifdef KMK
1644 if (utf16_regular_file_p(wszPathBuf))
1645#else
1646 dwAttribs = GetFileAttributesW(wszPathBuf);
1647 if ( dwAttribs != INVALID_FILE_ATTRIBUTES
1648 && !(dwAttribs & FILE_ATTRIBUTE_DIRECTORY))
1649#endif
1650 return mkWinChildDuplicateUtf16String(wszPathBuf, cwcCombined + (fHasExeSuffix ? 0 : 4), ppwszImagePath);
1651 if (!fHasExeSuffix)
1652 {
1653 wszPathBuf[cwcCombined - 1] = L'\0';
1654#ifdef KMK
1655 if (utf16_regular_file_p(wszPathBuf))
1656#endif
1657 {
1658 /*
1659 * Check if the file exists w/o the added '.exe' suffix. If it does,
1660 * we need to check if we can pass it to CreateProcess or need the shell.
1661 */
1662 hFile = CreateFileW(wszPathBuf, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
1663 NULL /*pSecAttr*/, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1664 if (hFile != INVALID_HANDLE_VALUE)
1665 {
1666 *pfNeedShell = mkWinChildcareWorkerCheckIfNeedShell(hFile);
1667 CloseHandle(hFile);
1668 if (!*pfNeedShell)
1669 return mkWinChildDuplicateUtf16String(wszPathBuf, cwcCombined, ppwszImagePath);
1670 break;
1671 }
1672 }
1673 }
1674 }
1675
1676 /*
1677 * Advance to the next component.
1678 */
1679 if (wcEnd != '\0')
1680 pwszSearchPath += cwcSkip + 1;
1681 else if (fSearchedCwd)
1682 break;
1683 else
1684 {
1685 fSearchedCwd = TRUE;
1686 wszPathFallback[0] = L'.';
1687 wszPathFallback[1] = L'\0';
1688 pwszSearchPath = wszPathFallback;
1689 }
1690 }
1691 }
1692
1693 /*
1694 * We need the shell. It will take care of finding/reporting missing
1695 * image files and such.
1696 */
1697 *pfNeedShell = TRUE;
1698 if (pszShell)
1699 {
1700 cwc = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszShell, strlen(pszShell) + 1, wszPathBuf, MKWINCHILD_MAX_PATH);
1701 if (cwc > 0)
1702 return mkWinChildDuplicateUtf16String(wszPathBuf, cwc, ppwszImagePath);
1703 dwErr = GetLastError();
1704 fprintf(stderr, _("MultiByteToWideChar failed to convert shell (%s): %u\n"), pszShell, dwErr);
1705 }
1706 else
1707 {
1708 fprintf(stderr, "%s: not found!\n", pszArg0);
1709//__debugbreak();
1710 dwErr = ERROR_FILE_NOT_FOUND;
1711 }
1712 }
1713 else
1714 {
1715 dwErr = GetLastError();
1716 fprintf(stderr, _("MultiByteToWideChar failed to convert argv[0] (%s): %u\n"), pszArg0, dwErr);
1717 }
1718 return dwErr == ERROR_INSUFFICIENT_BUFFER ? ERROR_FILENAME_EXCED_RANGE : dwErr;
1719}
1720
1721/**
1722 * Creates the environment block.
1723 *
1724 * @returns 0 on success, windows error code on failure.
1725 * @param papszEnv The environment vector to convert.
1726 * @param cbEnvStrings The size of the environment strings, iff they are
1727 * sequential in a block. Otherwise, zero.
1728 * @param ppwszEnv Where to return the pointer to the environment
1729 * block.
1730 * @param ppwszSearchPath Where to return the pointer to the path value
1731 * within the environment block. This will not be set
1732 * if cbEnvStrings is non-zero, more efficient to let
1733 * mkWinChildcareWorkerFindImage() search when needed.
1734 */
1735static int mkWinChildcareWorkerConvertEnvironment(char **papszEnv, size_t cbEnvStrings,
1736 WCHAR **ppwszEnv, WCHAR const **ppwszSearchPath)
1737{
1738 DWORD dwErr;
1739 int cwcRc;
1740 int cwcDst;
1741 WCHAR *pwszzDst;
1742
1743 *ppwszSearchPath = NULL;
1744
1745 /*
1746 * We've got a little optimization here with help from mkWinChildCopyStringArray.
1747 */
1748 if (cbEnvStrings)
1749 {
1750 cwcDst = cbEnvStrings + 32;
1751 pwszzDst = (WCHAR *)xmalloc(cwcDst * sizeof(WCHAR));
1752 cwcRc = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, papszEnv[0], cbEnvStrings, pwszzDst, cwcDst);
1753 if (cwcRc != 0)
1754 {
1755 *ppwszEnv = pwszzDst;
1756 return 0;
1757 }
1758
1759 /* Resize the allocation and try again. */
1760 dwErr = GetLastError();
1761 if (dwErr == ERROR_INSUFFICIENT_BUFFER)
1762 {
1763 cwcRc = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, papszEnv[0], cbEnvStrings, NULL, 0);
1764 if (cwcRc > 0)
1765 cwcDst = cwcRc + 32;
1766 else
1767 cwcDst *= 2;
1768 pwszzDst = (WCHAR *)xrealloc(pwszzDst, cwcDst);
1769 cwcRc = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, papszEnv[0], cbEnvStrings, pwszzDst, cwcDst);
1770 if (cwcRc != 0)
1771 {
1772 *ppwszEnv = pwszzDst;
1773 return 0;
1774 }
1775 dwErr = GetLastError();
1776 }
1777 fprintf(stderr, _("MultiByteToWideChar failed to convert environment block: %u\n"), dwErr);
1778 }
1779 /*
1780 * Need to convert it string by string.
1781 */
1782 else
1783 {
1784 size_t offPathValue = ~(size_t)0;
1785 size_t offDst;
1786
1787 /*
1788 * Estimate the size first.
1789 */
1790 size_t cEnvVars;
1791 size_t cwcDst = 32;
1792 size_t iVar = 0;
1793 const char *pszSrc;
1794 while ((pszSrc = papszEnv[iVar]) != NULL)
1795 {
1796 cwcDst += strlen(pszSrc) + 1;
1797 iVar++;
1798 }
1799 cEnvVars = iVar;
1800
1801 /* Allocate estimated WCHARs and convert the variables one by one, reallocating
1802 the block as needed. */
1803 pwszzDst = (WCHAR *)xmalloc(cwcDst * sizeof(WCHAR));
1804 cwcDst--; /* save one wchar for the terminating empty string. */
1805 offDst = 0;
1806 for (iVar = 0; iVar < cEnvVars; iVar++)
1807 {
1808 size_t cwcLeft = cwcDst - offDst;
1809 size_t const cbSrc = strlen(pszSrc = papszEnv[iVar]) + 1;
1810 assert(cwcDst >= offDst);
1811
1812
1813 cwcRc = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszSrc, cbSrc, &pwszzDst[offDst], cwcLeft);
1814 if (cwcRc > 0)
1815 { /* likely */ }
1816 else
1817 {
1818 dwErr = GetLastError();
1819 if (dwErr == ERROR_INSUFFICIENT_BUFFER)
1820 {
1821 /* Need more space. So, calc exacly how much and resize the block accordingly. */
1822 size_t cbSrc2 = cbSrc;
1823 size_t iVar2 = iVar;
1824 cwcLeft = 1;
1825 for (;;)
1826 {
1827 size_t cwcRc2 = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszSrc, cbSrc, NULL, 0);
1828 if (cwcRc2 > 0)
1829 cwcLeft += cwcRc2;
1830 else
1831 cwcLeft += cbSrc * 4;
1832
1833 /* advance */
1834 iVar2++;
1835 if (iVar2 >= cEnvVars)
1836 break;
1837 pszSrc = papszEnv[iVar2];
1838 cbSrc2 = strlen(pszSrc) + 1;
1839 }
1840 pszSrc = papszEnv[iVar];
1841
1842 /* Grow the allocation and repeat the conversion. */
1843 if (offDst + cwcLeft > cwcDst + 1)
1844 {
1845 cwcDst = offDst + cwcLeft;
1846 pwszzDst = (WCHAR *)xrealloc(pwszzDst, cwcDst * sizeof(WCHAR));
1847 cwcDst--; /* save one wchar for the terminating empty string. */
1848 cwcRc = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszSrc, cbSrc, &pwszzDst[offDst], cwcLeft - 1);
1849 if (cwcRc <= 0)
1850 dwErr = GetLastError();
1851 }
1852 }
1853 if (cwcRc <= 0)
1854 {
1855 fprintf(stderr, _("MultiByteToWideChar failed to convert environment string #%u (%s): %u\n"),
1856 iVar, pszSrc, dwErr);
1857 free(pwszzDst);
1858 return dwErr;
1859 }
1860 }
1861
1862 /* Look for the PATH. */
1863 if ( offPathValue == ~(size_t)0
1864 && IS_PATH_ENV_VAR(cwcRc, &pwszzDst[offDst]) )
1865 offPathValue = offDst + 4 + 1;
1866
1867 /* Advance. */
1868 offDst += cwcRc;
1869 }
1870 pwszzDst[offDst++] = '\0';
1871
1872 if (offPathValue != ~(size_t)0)
1873 *ppwszSearchPath = &pwszzDst[offPathValue];
1874 *ppwszEnv = pwszzDst;
1875 return 0;
1876 }
1877 free(pwszzDst);
1878 return dwErr;
1879}
1880
1881/**
1882 * Childcare worker: handle regular process.
1883 *
1884 * @param pWorker The worker.
1885 * @param pChild The kSubmit child.
1886 */
1887static void mkWinChildcareWorkerThreadHandleProcess(PWINCHILDCAREWORKER pWorker, PWINCHILD pChild)
1888{
1889 WCHAR *pwszSearchPath = NULL;
1890 WCHAR *pwszzEnvironment = NULL;
1891 WCHAR *pwszCommandLine = NULL;
1892 WCHAR *pwszImageName = NULL;
1893 BOOL fNeedShell = FALSE;
1894 int rc;
1895
1896 /*
1897 * First we convert the environment so we get the PATH we need to
1898 * search for the executable.
1899 */
1900 rc = mkWinChildcareWorkerConvertEnvironment(pChild->u.Process.papszEnv ? pChild->u.Process.papszEnv : environ,
1901 pChild->u.Process.cbEnvStrings,
1902 &pwszzEnvironment, &pwszSearchPath);
1903 /*
1904 * Find the executable and maybe checking if it's a shell script, then
1905 * convert it to a command line.
1906 */
1907 if (rc == 0)
1908 rc = mkWinChildcareWorkerFindImage(pChild->u.Process.papszArgs[0], pwszSearchPath, pwszzEnvironment,
1909 pChild->u.Process.pszShell, &pwszImageName, &fNeedShell);
1910 if (rc == 0)
1911 {
1912 if (!fNeedShell)
1913 rc = mkWinChildcareWorkerConvertCommandline(pChild->u.Process.papszArgs, 0 /*fFlags*/, &pwszCommandLine);
1914 else
1915 rc = mkWinChildcareWorkerConvertCommandlineWithShell(pwszImageName, pChild->u.Process.papszArgs, &pwszCommandLine);
1916
1917 /*
1918 * Create the child process.
1919 */
1920 if (rc == 0)
1921 {
1922 rc = mkWinChildcareWorkerCreateProcess(pWorker, pChild, pwszImageName, pwszCommandLine, pwszzEnvironment);
1923 if (rc == 0)
1924 {
1925 /*
1926 * Wait for the child to complete.
1927 */
1928 mkWinChildcareWorkerWaitForProcess(pWorker, pChild, pChild->u.Process.hProcess, pwszImageName,
1929 pChild->u.Process.fCatchOutput);
1930 }
1931 else
1932 pChild->iExitCode = rc;
1933 }
1934 else
1935 pChild->iExitCode = rc;
1936 }
1937 else
1938 pChild->iExitCode = rc;
1939 free(pwszCommandLine);
1940 free(pwszImageName);
1941 free(pwszzEnvironment);
1942
1943 /* In case we failed, we must make sure the child end of pipes
1944 used by $(shell no_such_command.exe) are closed, otherwise
1945 the main thread will be stuck reading the parent end. */
1946 mkWinChildcareWorkerCloseStandardHandles(pChild);
1947}
1948
1949#ifdef KMK
1950
1951/**
1952 * Childcare worker: handle builtin command.
1953 *
1954 * @param pWorker The worker.
1955 * @param pChild The kSubmit child.
1956 */
1957static void mkWinChildcareWorkerThreadHandleBuiltIn(PWINCHILDCAREWORKER pWorker, PWINCHILD pChild)
1958{
1959 PCKMKBUILTINENTRY pBuiltIn = pChild->u.BuiltIn.pBuiltIn;
1960 if (pBuiltIn->uFnSignature == FN_SIG_MAIN)
1961 pChild->iExitCode = pBuiltIn->u.pfnMain(pChild->u.BuiltIn.cArgs, pChild->u.BuiltIn.papszArgs, pChild->u.BuiltIn.papszEnv);
1962 else if (pBuiltIn->uFnSignature == FN_SIG_MAIN_SPAWNS)
1963 pChild->iExitCode = pBuiltIn->u.pfnMainSpawns(pChild->u.BuiltIn.cArgs, pChild->u.BuiltIn.papszArgs,
1964 pChild->u.BuiltIn.papszEnv, pChild->pMkChild, NULL);
1965 else
1966 {
1967 assert(0);
1968 pChild->iExitCode = 98;
1969 }
1970}
1971
1972/**
1973 * Childcare worker: handle append write-out.
1974 *
1975 * @param pWorker The worker.
1976 * @param pChild The kSubmit child.
1977 */
1978static void mkWinChildcareWorkerThreadHandleAppend(PWINCHILDCAREWORKER pWorker, PWINCHILD pChild)
1979{
1980 int fd = open(pChild->u.Append.pszFilename,
1981 pChild->u.Append.fTruncate
1982 ? O_WRONLY | O_TRUNC | O_CREAT | _O_NOINHERIT | _O_BINARY
1983 : O_WRONLY | O_APPEND | O_CREAT | _O_NOINHERIT | _O_BINARY,
1984 0666);
1985 if (fd >= 0)
1986 {
1987 ssize_t cbWritten = write(fd, pChild->u.Append.pszAppend, pChild->u.Append.cbAppend);
1988 if (cbWritten == (ssize_t)pChild->u.Append.cbAppend)
1989 {
1990 if (close(fd) >= 0)
1991 {
1992 pChild->iExitCode = 0;
1993 return;
1994 }
1995 fprintf(stderr, "kmk_builtin_append: close failed on '%s': %u (%s)\n",
1996 pChild->u.Append.pszFilename, errno, strerror(errno));
1997 }
1998 else
1999 fprintf(stderr, "kmk_builtin_append: error writing %lu bytes to on '%s': %u (%s)\n",
2000 pChild->u.Append.cbAppend, pChild->u.Append.pszFilename, errno, strerror(errno));
2001 close(fd);
2002 }
2003 else
2004 fprintf(stderr, "kmk_builtin_append: error opening '%s': %u (%s)\n",
2005 pChild->u.Append.pszFilename, errno, strerror(errno));
2006 pChild->iExitCode = 1;
2007}
2008
2009/**
2010 * Childcare worker: handle kSubmit job.
2011 *
2012 * @param pWorker The worker.
2013 * @param pChild The kSubmit child.
2014 */
2015static void mkWinChildcareWorkerThreadHandleSubmit(PWINCHILDCAREWORKER pWorker, PWINCHILD pChild)
2016{
2017 void *pvSubmitWorker = pChild->u.Submit.pvSubmitWorker;
2018 for (;;)
2019 {
2020 int iExitCode = -42;
2021 int iSignal = -1;
2022 DWORD dwStatus = WaitForSingleObject(pChild->u.Submit.hEvent, INFINITE);
2023 assert(dwStatus != WAIT_FAILED);
2024
2025 if (kSubmitSubProcGetResult((intptr_t)pvSubmitWorker, &iExitCode, &iSignal) == 0)
2026 {
2027 pChild->iExitCode = iExitCode;
2028 pChild->iSignal = iSignal;
2029 /* Cleanup must be done on the main thread. */
2030 return;
2031 }
2032
2033 if (pChild->iSignal != 0)
2034 kSubmitSubProcKill((intptr_t)pvSubmitWorker, pChild->iSignal);
2035 }
2036}
2037
2038/**
2039 * Childcare worker: handle kmk_redirect process.
2040 *
2041 * @param pWorker The worker.
2042 * @param pChild The redirect child.
2043 */
2044static void mkWinChildcareWorkerThreadHandleRedirect(PWINCHILDCAREWORKER pWorker, PWINCHILD pChild)
2045{
2046 mkWinChildcareWorkerWaitForProcess(pWorker, pChild, pChild->u.Redirect.hProcess, L"kmk_redirect", FALSE /*fCatchOutput*/);
2047}
2048
2049#endif /* KMK */
2050
2051/**
2052 * Childcare worker thread.
2053 *
2054 * @returns 0
2055 * @param pvUser The worker instance.
2056 */
2057static unsigned int __stdcall mkWinChildcareWorkerThread(void *pvUser)
2058{
2059 PWINCHILDCAREWORKER pWorker = (PWINCHILDCAREWORKER)pvUser;
2060 assert(pWorker->uMagic == WINCHILDCAREWORKER_MAGIC);
2061
2062 /*
2063 * Adjust process group if necessary.
2064 */
2065 if (g_cProcessorGroups > 1)
2066 {
2067 GROUP_AFFINITY Affinity = { ~(ULONG_PTR)0, pWorker->iProcessorGroup, { 0, 0, 0 } };
2068 BOOL fRet = g_pfnSetThreadGroupAffinity(GetCurrentThread(), &Affinity, NULL);
2069 assert(fRet); (void)fRet;
2070 }
2071
2072 /*
2073 * Work loop.
2074 */
2075 while (!g_fShutdown)
2076 {
2077 /*
2078 * Try go idle.
2079 */
2080 PWINCHILD pChild = pWorker->pTailTodoChildren;
2081 if (!pChild)
2082 {
2083 _InterlockedExchange(&pWorker->fIdle, TRUE);
2084 pChild = pWorker->pTailTodoChildren;
2085 if (!pChild)
2086 {
2087 DWORD dwStatus;
2088
2089 _InterlockedIncrement((long *)&g_cIdleChildcareWorkers);
2090 _InterlockedExchange((long *)&g_idxLastChildcareWorker, pWorker->idxWorker);
2091 dwStatus = WaitForSingleObject(pWorker->hEvtIdle, INFINITE);
2092 _InterlockedExchange(&pWorker->fIdle, FALSE);
2093 _InterlockedDecrement((long *)&g_cIdleChildcareWorkers);
2094
2095 assert(dwStatus != WAIT_FAILED);
2096 if (dwStatus == WAIT_FAILED)
2097 Sleep(20);
2098
2099 pChild = pWorker->pTailTodoChildren;
2100 }
2101 else
2102 _InterlockedExchange(&pWorker->fIdle, FALSE);
2103 }
2104 if (pChild)
2105 {
2106 /*
2107 * We got work to do. First job is to deque the job.
2108 */
2109 pChild = mkWinChildDequeFromLifo(&pWorker->pTailTodoChildren, pChild);
2110 assert(pChild);
2111 if (pChild)
2112 {
2113 PWINCHILD pTailExpect;
2114
2115 switch (pChild->enmType)
2116 {
2117 case WINCHILDTYPE_PROCESS:
2118 mkWinChildcareWorkerThreadHandleProcess(pWorker, pChild);
2119 break;
2120#ifdef KMK
2121 case WINCHILDTYPE_BUILT_IN:
2122 mkWinChildcareWorkerThreadHandleBuiltIn(pWorker, pChild);
2123 break;
2124 case WINCHILDTYPE_APPEND:
2125 mkWinChildcareWorkerThreadHandleAppend(pWorker, pChild);
2126 break;
2127 case WINCHILDTYPE_SUBMIT:
2128 mkWinChildcareWorkerThreadHandleSubmit(pWorker, pChild);
2129 break;
2130 case WINCHILDTYPE_REDIRECT:
2131 mkWinChildcareWorkerThreadHandleRedirect(pWorker, pChild);
2132 break;
2133#endif
2134 default:
2135 assert(0);
2136 }
2137
2138 /*
2139 * Move the child to the completed list.
2140 */
2141 pTailExpect = NULL;
2142 for (;;)
2143 {
2144 PWINCHILD pTailActual;
2145 pChild->pNext = pTailExpect;
2146 pTailActual = _InterlockedCompareExchangePointer(&g_pTailCompletedChildren, pChild, pTailExpect);
2147 if (pTailActual != pTailExpect)
2148 pTailExpect = pTailActual;
2149 else
2150 {
2151 _InterlockedDecrement(&g_cPendingChildren);
2152 if (pTailExpect)
2153 break;
2154 if (SetEvent(g_hEvtWaitChildren))
2155 break;
2156 fprintf(stderr, "SetEvent(g_hEvtWaitChildren=%p) failed: %u\n", g_hEvtWaitChildren, GetLastError());
2157 break;
2158 }
2159 }
2160 }
2161 }
2162 }
2163
2164 _endthreadex(0);
2165 return 0;
2166}
2167
2168/**
2169 * Creates a pipe for catching child output.
2170 *
2171 * This is a custom CreatePipe implementation that allows for overlapped I/O on
2172 * our end of the pipe. Silly that they don't offer an API that does this.
2173 *
2174 * @returns Success indicator.
2175 * @param pPipe The structure for the pipe.
2176 * @param iWhich Which standard descriptor this is a pipe for.
2177 * @param idxWorker The worker index.
2178 */
2179static BOOL mkWinChildcareCreateWorkerPipe(PWINCCWPIPE pPipe, unsigned iWhich, unsigned int idxWorker)
2180{
2181 /*
2182 * We try generate a reasonably unique name from the get go, so this retry
2183 * loop shouldn't really ever be needed. But you never know.
2184 */
2185 static unsigned s_iSeqNo = 0;
2186 DWORD const cMaxInstances = 1;
2187 DWORD const cbPipe = 4096;
2188 DWORD const cMsTimeout = 0;
2189 unsigned cTries = 256;
2190 while (cTries-- > 0)
2191 {
2192 /* Create the pipe (our end). */
2193 HANDLE hPipeRead;
2194 DWORD fOpenMode = PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED | FILE_FLAG_FIRST_PIPE_INSTANCE;
2195 DWORD fPipeMode = PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT | PIPE_REJECT_REMOTE_CLIENTS;
2196 WCHAR wszName[MAX_PATH];
2197 s_iSeqNo++;
2198 _snwprintf(wszName, MAX_PATH, L"\\\\.\\pipe\\kmk-winchildren-%u-%u-%u-%s-%u-%u",
2199 GetCurrentProcessId(), GetCurrentThreadId(), idxWorker, iWhich == 1 ? L"out" : L"err", s_iSeqNo, GetTickCount());
2200 hPipeRead = CreateNamedPipeW(wszName, fOpenMode, fPipeMode, cMaxInstances, cbPipe, cbPipe, cMsTimeout, NULL /*pSecAttr*/);
2201 if (hPipeRead == INVALID_HANDLE_VALUE && GetLastError() == ERROR_INVALID_PARAMETER)
2202 {
2203 fOpenMode &= ~FILE_FLAG_FIRST_PIPE_INSTANCE;
2204 fPipeMode &= ~PIPE_REJECT_REMOTE_CLIENTS;
2205 hPipeRead = CreateNamedPipeW(wszName, fOpenMode, fPipeMode, cMaxInstances, cbPipe, cbPipe, cMsTimeout, NULL /*pSecAttr*/);
2206 }
2207 if (hPipeRead != INVALID_HANDLE_VALUE)
2208 {
2209 /* Connect the other end. */
2210 HANDLE hPipeWrite = CreateFileW(wszName, GENERIC_WRITE | FILE_READ_ATTRIBUTES, 0 /*fShareMode*/, NULL /*pSecAttr*/,
2211 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL /*hTemplateFile*/);
2212 if (hPipeWrite != INVALID_HANDLE_VALUE)
2213 {
2214 /*
2215 * Create the event object and we're done.
2216 *
2217 * It starts in signalled stated so we don't need special code
2218 * for handing when we start waiting.
2219 */
2220 HANDLE hEvent = CreateEventW(NULL /*pSecAttr*/, TRUE /*fManualReset*/, TRUE /*fInitialState*/, NULL /*pwszName*/);
2221 if (hEvent != NULL)
2222 {
2223 pPipe->hPipeMine = hPipeRead;
2224 pPipe->hPipeChild = hPipeWrite;
2225 pPipe->hEvent = hEvent;
2226 pPipe->iWhich = iWhich;
2227 pPipe->fReadPending = FALSE;
2228 pPipe->cbBuffer = cbPipe;
2229 pPipe->pbBuffer = xcalloc(cbPipe);
2230 return TRUE;
2231 }
2232
2233 CloseHandle(hPipeWrite);
2234 CloseHandle(hPipeRead);
2235 return FALSE;
2236 }
2237 CloseHandle(hPipeRead);
2238 }
2239 }
2240 return FALSE;
2241}
2242
2243/**
2244 * Destroys a childcare worker pipe.
2245 *
2246 * @param pPipe The pipe.
2247 */
2248static void mkWinChildcareDeleteWorkerPipe(PWINCCWPIPE pPipe)
2249{
2250 if (pPipe->hPipeChild != NULL)
2251 {
2252 CloseHandle(pPipe->hPipeChild);
2253 pPipe->hPipeChild = NULL;
2254 }
2255
2256 if (pPipe->hPipeMine != NULL)
2257 {
2258 if (pPipe->fReadPending)
2259 if (!CancelIo(pPipe->hPipeMine))
2260 WaitForSingleObject(pPipe->hEvent, INFINITE);
2261 CloseHandle(pPipe->hPipeMine);
2262 pPipe->hPipeMine = NULL;
2263 }
2264
2265 if (pPipe->hEvent != NULL)
2266 {
2267 CloseHandle(pPipe->hEvent);
2268 pPipe->hEvent = NULL;
2269 }
2270
2271 if (pPipe->pbBuffer)
2272 {
2273 free(pPipe->pbBuffer);
2274 pPipe->pbBuffer = NULL;
2275 }
2276}
2277
2278/**
2279 * Creates another childcare worker.
2280 *
2281 * @returns The new worker, if we succeeded.
2282 */
2283static PWINCHILDCAREWORKER mkWinChildcareCreateWorker(void)
2284{
2285 PWINCHILDCAREWORKER pWorker = (PWINCHILDCAREWORKER)xcalloc(sizeof(*pWorker));
2286 pWorker->uMagic = WINCHILDCAREWORKER_MAGIC;
2287 pWorker->idxWorker = g_cChildCareworkers;
2288 pWorker->hEvtIdle = CreateEventW(NULL, FALSE /*fManualReset*/, FALSE /*fInitialState*/, NULL /*pwszName*/);
2289 if (pWorker->hEvtIdle)
2290 {
2291 if (mkWinChildcareCreateWorkerPipe(&pWorker->StdOut, 1, pWorker->idxWorker))
2292 {
2293 if (mkWinChildcareCreateWorkerPipe(&pWorker->StdErr, 2, pWorker->idxWorker))
2294 {
2295 /* Before we start the thread, assign it to a processor group. */
2296 if (g_cProcessorGroups > 1)
2297 {
2298 unsigned int cMaxInGroup;
2299 unsigned int cInGroup;
2300 unsigned int iGroup = g_idxProcessorGroupAllocator % g_cProcessorGroups;
2301 pWorker->iProcessorGroup = iGroup;
2302
2303 /* Advance. We employ a very simple strategy that does 50% in
2304 each group for each group cycle. Odd processor counts are
2305 caught in odd group cycles. The init function selects the
2306 starting group based on make nesting level to avoid stressing
2307 out the first group. */
2308 cInGroup = ++g_idxProcessorInGroupAllocator;
2309 cMaxInGroup = g_pacProcessorsInGroup[iGroup];
2310 if ( !(cMaxInGroup & 1)
2311 || !((g_idxProcessorGroupAllocator / g_cProcessorGroups) & 1))
2312 cMaxInGroup /= 2;
2313 else
2314 cMaxInGroup = cMaxInGroup / 2 + 1;
2315 if (cInGroup >= cMaxInGroup)
2316 {
2317 g_idxProcessorInGroupAllocator = 0;
2318 g_idxProcessorGroupAllocator++;
2319 }
2320 }
2321
2322 /* Try start the thread. */
2323 pWorker->hThread = (HANDLE)_beginthreadex(NULL, 0 /*cbStack*/, mkWinChildcareWorkerThread, pWorker,
2324 0, &pWorker->tid);
2325 if (pWorker->hThread != NULL)
2326 {
2327 pWorker->idxWorker = g_cChildCareworkers++; /* paranoia */
2328 g_papChildCareworkers[pWorker->idxWorker] = pWorker;
2329 return pWorker;
2330 }
2331
2332 /* Bail out! */
2333 fprintf(stderr, "warning! _beginthreadex failed: %u (%s)\n", errno, strerror(errno));
2334 mkWinChildcareDeleteWorkerPipe(&pWorker->StdErr);
2335 }
2336 else
2337 fprintf(stderr, "warning! Failed to create stderr pipe: %u\n", GetLastError());
2338 mkWinChildcareDeleteWorkerPipe(&pWorker->StdOut);
2339 }
2340 else
2341 fprintf(stderr, "warning! Failed to create stdout pipe: %u\n", GetLastError());
2342 CloseHandle(pWorker->hEvtIdle);
2343 }
2344 else
2345 fprintf(stderr, "warning! CreateEvent failed: %u\n", GetLastError());
2346 pWorker->uMagic = ~WINCHILDCAREWORKER_MAGIC;
2347 free(pWorker);
2348 return NULL;
2349}
2350
2351/**
2352 * Helper for copying argument and environment vectors.
2353 *
2354 * @returns Single alloc block copy.
2355 * @param papszSrc The source vector.
2356 * @param pcbStrings Where to return the size of the strings & terminator.
2357 */
2358static char **mkWinChildCopyStringArray(char **papszSrc, size_t *pcbStrings)
2359{
2360 const char *psz;
2361 char **papszDstArray;
2362 char *pszDstStr;
2363 size_t i;
2364
2365 /* Calc sizes first. */
2366 size_t cbStrings = 1; /* (one extra for terminator string) */
2367 size_t cStrings = 0;
2368 while ((psz = papszSrc[cStrings]) != NULL)
2369 {
2370 cbStrings += strlen(psz) + 1;
2371 cStrings++;
2372 }
2373 *pcbStrings = cbStrings;
2374
2375 /* Allocate destination. */
2376 papszDstArray = (char **)xmalloc(cbStrings + (cStrings + 1) * sizeof(papszDstArray[0]));
2377 pszDstStr = (char *)&papszDstArray[cStrings + 1];
2378
2379 /* Copy it. */
2380 for (i = 0; i < cStrings; i++)
2381 {
2382 const char *pszSource = papszSrc[i];
2383 size_t cchString = strlen(pszSource);
2384 papszDstArray[i] = pszDstStr;
2385 memcpy(pszDstStr, pszSource, cchString);
2386 pszDstStr += cchString;
2387 *pszDstStr++ = '\0';
2388 }
2389 *pszDstStr = '\0';
2390 assert(&pszDstStr[1] - papszDstArray[0] == cbStrings);
2391 papszDstArray[i] = NULL;
2392 return papszDstArray;
2393}
2394
2395/**
2396 * Allocate and init a WINCHILD.
2397 *
2398 * @returns The new windows child structure.
2399 * @param enmType The child type.
2400 */
2401static PWINCHILD mkWinChildNew(WINCHILDTYPE enmType)
2402{
2403 PWINCHILD pChild = xcalloc(sizeof(*pChild));
2404 pChild->enmType = enmType;
2405 pChild->fCoreDumped = 0;
2406 pChild->iSignal = 0;
2407 pChild->iExitCode = 222222;
2408 pChild->uMagic = WINCHILD_MAGIC;
2409 pChild->pid = (intptr_t)pChild;
2410 return pChild;
2411}
2412
2413/**
2414 * Destructor for WINCHILD.
2415 *
2416 * @param pChild The child structure to destroy.
2417 */
2418static void mkWinChildDelete(PWINCHILD pChild)
2419{
2420 assert(pChild->uMagic == WINCHILD_MAGIC);
2421 pChild->uMagic = ~WINCHILD_MAGIC;
2422
2423 switch (pChild->enmType)
2424 {
2425 case WINCHILDTYPE_PROCESS:
2426 {
2427 if (pChild->u.Process.papszArgs)
2428 {
2429 free(pChild->u.Process.papszArgs);
2430 pChild->u.Process.papszArgs = NULL;
2431 }
2432 if (pChild->u.Process.cbEnvStrings && pChild->u.Process.papszEnv)
2433 {
2434 free(pChild->u.Process.papszEnv);
2435 pChild->u.Process.papszEnv = NULL;
2436 }
2437 if (pChild->u.Process.pszShell)
2438 {
2439 free(pChild->u.Process.pszShell);
2440 pChild->u.Process.pszShell = NULL;
2441 }
2442 if (pChild->u.Process.hProcess)
2443 {
2444 CloseHandle(pChild->u.Process.hProcess);
2445 pChild->u.Process.hProcess = NULL;
2446 }
2447 mkWinChildcareWorkerCloseStandardHandles(pChild);
2448 break;
2449 }
2450
2451#ifdef KMK
2452 case WINCHILDTYPE_BUILT_IN:
2453 if (pChild->u.BuiltIn.papszArgs)
2454 {
2455 free(pChild->u.BuiltIn.papszArgs);
2456 pChild->u.BuiltIn.papszArgs = NULL;
2457 }
2458 if (pChild->u.BuiltIn.papszEnv)
2459 {
2460 free(pChild->u.BuiltIn.papszEnv);
2461 pChild->u.BuiltIn.papszEnv = NULL;
2462 }
2463 break;
2464
2465 case WINCHILDTYPE_APPEND:
2466 if (pChild->u.Append.pszFilename)
2467 {
2468 free(pChild->u.Append.pszFilename);
2469 pChild->u.Append.pszFilename = NULL;
2470 }
2471 if (pChild->u.Append.pszAppend)
2472 {
2473 free(pChild->u.Append.pszAppend);
2474 pChild->u.Append.pszAppend = NULL;
2475 }
2476 break;
2477
2478 case WINCHILDTYPE_SUBMIT:
2479 if (pChild->u.Submit.pvSubmitWorker)
2480 {
2481 kSubmitSubProcCleanup((intptr_t)pChild->u.Submit.pvSubmitWorker);
2482 pChild->u.Submit.pvSubmitWorker = NULL;
2483 }
2484 break;
2485
2486 case WINCHILDTYPE_REDIRECT:
2487 if (pChild->u.Redirect.hProcess)
2488 {
2489 CloseHandle(pChild->u.Redirect.hProcess);
2490 pChild->u.Redirect.hProcess = NULL;
2491 }
2492 break;
2493#endif /* KMK */
2494
2495 default:
2496 assert(0);
2497 }
2498
2499 free(pChild);
2500}
2501
2502/**
2503 * Queues the child with a worker, creating new workers if necessary.
2504 *
2505 * @returns 0 on success, windows error code on failure (child destroyed).
2506 * @param pChild The child.
2507 * @param pPid Where to return the PID (optional).
2508 */
2509static int mkWinChildPushToCareWorker(PWINCHILD pChild, pid_t *pPid)
2510{
2511 PWINCHILDCAREWORKER pWorker = NULL;
2512 PWINCHILD pOldChild;
2513 PWINCHILD pCurChild;
2514
2515 /*
2516 * There are usually idle workers around, except for at the start.
2517 */
2518 if (g_cIdleChildcareWorkers > 0)
2519 {
2520 /*
2521 * Try the idle hint first and move forward from it.
2522 */
2523 unsigned int const cWorkers = g_cChildCareworkers;
2524 unsigned int iHint = g_idxLastChildcareWorker;
2525 unsigned int i;
2526 for (i = iHint; i < cWorkers; i++)
2527 {
2528 PWINCHILDCAREWORKER pPossibleWorker = g_papChildCareworkers[i];
2529 if (pPossibleWorker->fIdle)
2530 {
2531 pWorker = pPossibleWorker;
2532 break;
2533 }
2534 }
2535 if (!pWorker)
2536 {
2537 /* Scan from the start. */
2538 if (iHint > cWorkers)
2539 iHint = cWorkers;
2540 for (i = 0; i < iHint; i++)
2541 {
2542 PWINCHILDCAREWORKER pPossibleWorker = g_papChildCareworkers[i];
2543 if (pPossibleWorker->fIdle)
2544 {
2545 pWorker = pPossibleWorker;
2546 break;
2547 }
2548 }
2549 }
2550 }
2551 if (!pWorker)
2552 {
2553 /*
2554 * Try create more workers if we haven't reached the max yet.
2555 */
2556 if (g_cChildCareworkers < g_cChildCareworkersMax)
2557 pWorker = mkWinChildcareCreateWorker();
2558
2559 /*
2560 * Queue it with an existing worker. Look for one without anthing extra scheduled.
2561 */
2562 if (!pWorker)
2563 {
2564 unsigned int i = g_cChildCareworkers;
2565 if (i == 0)
2566 fatal(NILF, 0, _("Failed to create worker threads for managing child processes!\n"));
2567 pWorker = g_papChildCareworkers[--i];
2568 if (pWorker->pTailTodoChildren)
2569 while (i-- > 0)
2570 {
2571 PWINCHILDCAREWORKER pPossibleWorker = g_papChildCareworkers[i];
2572 if (!pPossibleWorker->pTailTodoChildren)
2573 {
2574 pWorker = pPossibleWorker;
2575 break;
2576 }
2577 }
2578 }
2579 }
2580
2581 /*
2582 * Do the queueing.
2583 */
2584 pOldChild = NULL;
2585 for (;;)
2586 {
2587 pChild->pNext = pOldChild;
2588 pCurChild = _InterlockedCompareExchangePointer((void **)&pWorker->pTailTodoChildren, pChild, pOldChild);
2589 if (pCurChild == pOldChild)
2590 {
2591 DWORD volatile dwErr;
2592 _InterlockedIncrement(&g_cPendingChildren);
2593 if ( !pWorker->fIdle
2594 || SetEvent(pWorker->hEvtIdle))
2595 {
2596 *pPid = pChild->pid;
2597 return 0;
2598 }
2599
2600 _InterlockedDecrement(&g_cPendingChildren);
2601 dwErr = GetLastError();
2602 assert(0);
2603 mkWinChildDelete(pChild);
2604 return dwErr ? dwErr : -20;
2605 }
2606 pOldChild = pCurChild;
2607 }
2608}
2609
2610/**
2611 * Creates a regular child process (job.c).
2612 *
2613 * Will copy the information and push it to a childcare thread that does the
2614 * actual process creation.
2615 *
2616 * @returns 0 on success, windows status code on failure.
2617 * @param papszArgs The arguments.
2618 * @param papszEnv The environment (optional).
2619 * @param pszShell The SHELL variable value (optional).
2620 * @param pMkChild The make child structure (optional).
2621 * @param pPid Where to return the pid.
2622 */
2623int MkWinChildCreate(char **papszArgs, char **papszEnv, const char *pszShell, struct child *pMkChild, pid_t *pPid)
2624{
2625 PWINCHILD pChild = mkWinChildNew(WINCHILDTYPE_PROCESS);
2626 pChild->pMkChild = pMkChild;
2627
2628 pChild->u.Process.papszArgs = mkWinChildCopyStringArray(papszArgs, &pChild->u.Process.cbArgsStrings);
2629 if ( !papszEnv
2630 || !pMkChild
2631 || pMkChild->environment == papszEnv)
2632 {
2633 pChild->u.Process.cbEnvStrings = 0;
2634 pChild->u.Process.papszEnv = papszEnv;
2635 }
2636 else
2637 pChild->u.Process.papszEnv = mkWinChildCopyStringArray(papszEnv, &pChild->u.Process.cbEnvStrings);
2638 if (pszShell)
2639 pChild->u.Process.pszShell = xstrdup(pszShell);
2640 pChild->u.Process.hStdOut = INVALID_HANDLE_VALUE;
2641 pChild->u.Process.hStdErr = INVALID_HANDLE_VALUE;
2642
2643 /* We always catch the output in order to prevent character soups courtesy
2644 of the microsoft CRT and/or linkers writing character by character to the
2645 console. Always try write whole lines, even when --output-sync is none. */
2646 pChild->u.Process.fCatchOutput = TRUE;
2647
2648 return mkWinChildPushToCareWorker(pChild, pPid);
2649}
2650
2651/**
2652 * Creates a chile process with a pipe hooked up to stdout.
2653 *
2654 * @returns 0 on success, non-zero on failure.
2655 * @param papszArgs The argument vector.
2656 * @param papszEnv The environment vector (optional).
2657 * @param fdErr File descriptor to hook up to stderr.
2658 * @param pPid Where to return the pid.
2659 * @param pfdReadPipe Where to return the read end of the pipe.
2660 */
2661int MkWinChildCreateWithStdOutPipe(char **papszArgs, char **papszEnv, int fdErr, pid_t *pPid, int *pfdReadPipe)
2662{
2663 /*
2664 * Create the pipe.
2665 */
2666 HANDLE hReadPipe;
2667 HANDLE hWritePipe;
2668 if (CreatePipe(&hReadPipe, &hWritePipe, NULL, 0 /* default size */))
2669 {
2670 if (SetHandleInformation(hWritePipe, HANDLE_FLAG_INHERIT /* clear */ , HANDLE_FLAG_INHERIT /*set*/))
2671 {
2672 int fdReadPipe = _open_osfhandle((intptr_t)hReadPipe, O_RDONLY);
2673 if (fdReadPipe >= 0)
2674 {
2675 PWINCHILD pChild;
2676 int rc;
2677
2678 /*
2679 * Get a handle for fdErr. Ignore failure.
2680 */
2681 HANDLE hStdErr = INVALID_HANDLE_VALUE;
2682 if (fdErr >= 0)
2683 {
2684 HANDLE hNative = (HANDLE)_get_osfhandle(fdErr);
2685 if (!DuplicateHandle(GetCurrentProcess(), hNative, GetCurrentProcess(),
2686 &hStdErr, 0 /*DesiredAccess*/, TRUE /*fInherit*/, DUPLICATE_SAME_ACCESS))
2687 {
2688 ONN(error, NILF, _("DuplicateHandle failed on stderr descriptor (%u): %u\n"), fdErr, GetLastError());
2689 hStdErr = INVALID_HANDLE_VALUE;
2690 }
2691 }
2692
2693 /*
2694 * Push it off to the worker thread.
2695 */
2696 pChild = mkWinChildNew(WINCHILDTYPE_PROCESS);
2697 pChild->u.Process.papszArgs = mkWinChildCopyStringArray(papszArgs, &pChild->u.Process.cbArgsStrings);
2698 pChild->u.Process.papszEnv = mkWinChildCopyStringArray(papszEnv ? papszEnv : environ,
2699 &pChild->u.Process.cbEnvStrings);
2700 //if (pszShell)
2701 // pChild->u.Process.pszShell = xstrdup(pszShell);
2702 pChild->u.Process.hStdOut = hWritePipe;
2703 pChild->u.Process.hStdErr = hStdErr;
2704 pChild->u.Process.fCloseStdErr = TRUE;
2705 pChild->u.Process.fCloseStdOut = TRUE;
2706
2707 rc = mkWinChildPushToCareWorker(pChild, pPid);
2708 if (rc == 0)
2709 *pfdReadPipe = fdReadPipe;
2710 else
2711 {
2712 ON(error, NILF, _("mkWinChildPushToCareWorker failed on pipe: %d\n"), rc);
2713 close(fdReadPipe);
2714 *pfdReadPipe = -1;
2715 *pPid = -1;
2716 }
2717 return rc;
2718 }
2719
2720 ON(error, NILF, _("_open_osfhandle failed on pipe: %u\n"), errno);
2721 }
2722 else
2723 ON(error, NILF, _("SetHandleInformation failed on pipe: %u\n"), GetLastError());
2724 if (hReadPipe != INVALID_HANDLE_VALUE)
2725 CloseHandle(hReadPipe);
2726 CloseHandle(hWritePipe);
2727 }
2728 else
2729 ON(error, NILF, _("CreatePipe failed: %u\n"), GetLastError());
2730 *pfdReadPipe = -1;
2731 *pPid = -1;
2732 return -1;
2733}
2734
2735#ifdef KMK
2736
2737/**
2738 * Interface used by kmkbuiltin.c for executing builtin commands on threads.
2739 *
2740 * @returns 0 on success, windows status code on failure.
2741 * @param pBuiltIn The kmk built-in command entry.
2742 * @param cArgs The number of arguments in papszArgs.
2743 * @param papszArgs The argument vector.
2744 * @param papszEnv The environment vector, optional.
2745 * @param pMkChild The make child structure.
2746 * @param pPid Where to return the pid.
2747 */
2748int MkWinChildCreateBuiltIn(PCKMKBUILTINENTRY pBuiltIn, int cArgs, char **papszArgs, char **papszEnv,
2749 struct child *pMkChild, pid_t *pPid)
2750{
2751 size_t cbIgnored;
2752 PWINCHILD pChild = mkWinChildNew(WINCHILDTYPE_BUILT_IN);
2753 pChild->pMkChild = pMkChild;
2754 pChild->u.BuiltIn.pBuiltIn = pBuiltIn;
2755 pChild->u.BuiltIn.cArgs = cArgs;
2756 pChild->u.BuiltIn.papszArgs = mkWinChildCopyStringArray(papszArgs, &cbIgnored);
2757 pChild->u.BuiltIn.papszEnv = papszEnv ? mkWinChildCopyStringArray(papszEnv, &cbIgnored) : NULL;
2758 return mkWinChildPushToCareWorker(pChild, pPid);
2759}
2760
2761/**
2762 * Interface used by append.c for do the slow file system part.
2763 *
2764 * This will append the given buffer to the specified file and free the buffer.
2765 *
2766 * @returns 0 on success, windows status code on failure.
2767 *
2768 * @param pszFilename The name of the file to append to.
2769 * @param ppszAppend What to append. The pointer pointed to is set to
2770 * NULL once we've taken ownership of the buffer and
2771 * promise to free it.
2772 * @param cbAppend How much to append.
2773 * @param fTruncate Whether to truncate the file before appending to it.
2774 * @param pMkChild The make child structure.
2775 * @param pPid Where to return the pid.
2776 */
2777int MkWinChildCreateAppend(const char *pszFilename, char **ppszAppend, size_t cbAppend, int fTruncate,
2778 struct child *pMkChild, pid_t *pPid)
2779{
2780 size_t cbFilename = strlen(pszFilename) + 1;
2781 PWINCHILD pChild = mkWinChildNew(WINCHILDTYPE_APPEND);
2782 pChild->pMkChild = pMkChild;
2783 pChild->u.Append.fTruncate = fTruncate;
2784 pChild->u.Append.pszAppend = *ppszAppend;
2785 pChild->u.Append.cbAppend = cbAppend;
2786 pChild->u.Append.pszFilename = (char *)memcpy(xmalloc(cbFilename), pszFilename, cbFilename);
2787 *ppszAppend = NULL;
2788 return mkWinChildPushToCareWorker(pChild, pPid);
2789}
2790
2791/**
2792 * Interface used by kSubmit.c for registering stuff to wait on.
2793 *
2794 * @returns 0 on success, windows status code on failure.
2795 * @param hEvent The event object handle to wait on.
2796 * @param pvSubmitWorker The argument to pass back to kSubmit to clean up.
2797 * @param pPid Where to return the pid.
2798 */
2799int MkWinChildCreateSubmit(intptr_t hEvent, void *pvSubmitWorker, pid_t *pPid)
2800{
2801 PWINCHILD pChild = mkWinChildNew(WINCHILDTYPE_SUBMIT);
2802 pChild->u.Submit.hEvent = (HANDLE)hEvent;
2803 pChild->u.Submit.pvSubmitWorker = pvSubmitWorker;
2804 return mkWinChildPushToCareWorker(pChild, pPid);
2805}
2806
2807/**
2808 * Interface used by redirect.c for registering stuff to wait on.
2809 *
2810 * @returns 0 on success, windows status code on failure.
2811 * @param hProcess The process object to wait on.
2812 * @param pPid Where to return the pid.
2813 */
2814int MkWinChildCreateRedirect(intptr_t hProcess, pid_t *pPid)
2815{
2816 PWINCHILD pChild = mkWinChildNew(WINCHILDTYPE_REDIRECT);
2817 pChild->u.Redirect.hProcess = (HANDLE)hProcess;
2818 return mkWinChildPushToCareWorker(pChild, pPid);
2819}
2820
2821#endif /* CONFIG_NEW_WIN_CHILDREN */
2822
2823/**
2824 * Interface used to kill process when processing Ctrl-C and fatal errors.
2825 *
2826 * @returns 0 on success, -1 & errno on error.
2827 * @param pid The process to kill (PWINCHILD).
2828 * @param iSignal What to kill it with.
2829 * @param pMkChild The make child structure for validation.
2830 */
2831int MkWinChildKill(pid_t pid, int iSignal, struct child *pMkChild)
2832{
2833 PWINCHILD pChild = (PWINCHILD)pid;
2834 if (pChild)
2835 {
2836 assert(pChild->uMagic == WINCHILD_MAGIC);
2837 if (pChild->uMagic == WINCHILD_MAGIC)
2838 {
2839 switch (pChild->enmType)
2840 {
2841 case WINCHILDTYPE_PROCESS:
2842 assert(pChild->pMkChild == pMkChild);
2843 TerminateProcess(pChild->u.Process.hProcess, DBG_TERMINATE_PROCESS);
2844 pChild->iSignal = iSignal;
2845 break;
2846#ifdef KMK
2847 case WINCHILDTYPE_SUBMIT:
2848 {
2849 pChild->iSignal = iSignal;
2850 SetEvent(pChild->u.Submit.hEvent);
2851 break;
2852 }
2853
2854 case WINCHILDTYPE_REDIRECT:
2855 TerminateProcess(pChild->u.Redirect.hProcess, DBG_TERMINATE_PROCESS);
2856 pChild->iSignal = iSignal;
2857 break;
2858
2859 case WINCHILDTYPE_BUILT_IN:
2860 break;
2861
2862#endif /* KMK */
2863 default:
2864 assert(0);
2865 }
2866 }
2867 }
2868 return -1;
2869}
2870
2871/**
2872 * Wait for a child process to complete
2873 *
2874 * @returns 0 on success, windows error code on failure.
2875 * @param fBlock Whether to block.
2876 * @param pPid Where to return the pid if a child process
2877 * completed. This is set to zero if none.
2878 * @param piExitCode Where to return the exit code.
2879 * @param piSignal Where to return the exit signal number.
2880 * @param pfCoreDumped Where to return the core dumped indicator.
2881 * @param ppMkChild Where to return the associated struct child pointer.
2882 */
2883int MkWinChildWait(int fBlock, pid_t *pPid, int *piExitCode, int *piSignal, int *pfCoreDumped, struct child **ppMkChild)
2884{
2885 PWINCHILD pChild;
2886
2887 *pPid = 0;
2888 *piExitCode = -222222;
2889 *pfCoreDumped = 0;
2890 *ppMkChild = NULL;
2891
2892 /*
2893 * Wait if necessary.
2894 */
2895 if (fBlock && !g_pTailCompletedChildren && g_cPendingChildren > 0)
2896 {
2897 DWORD dwStatus = WaitForSingleObject(g_hEvtWaitChildren, INFINITE);
2898 if (dwStatus == WAIT_FAILED)
2899 return (int)GetLastError();
2900 }
2901
2902 /*
2903 * Try unlink the last child in the LIFO.
2904 */
2905 pChild = g_pTailCompletedChildren;
2906 if (!pChild)
2907 return 0;
2908 pChild = mkWinChildDequeFromLifo(&g_pTailCompletedChildren, pChild);
2909 assert(pChild);
2910
2911 /*
2912 * Set return values and ditch the child structure.
2913 */
2914 *pPid = pChild->pid;
2915 *piExitCode = pChild->iExitCode;
2916 *pfCoreDumped = pChild->fCoreDumped;
2917 *ppMkChild = pChild->pMkChild;
2918 switch (pChild->enmType)
2919 {
2920 case WINCHILDTYPE_PROCESS:
2921 break;
2922#ifdef KMK
2923 case WINCHILDTYPE_BUILT_IN:
2924 case WINCHILDTYPE_APPEND:
2925 case WINCHILDTYPE_SUBMIT:
2926 case WINCHILDTYPE_REDIRECT:
2927 break;
2928#endif /* KMK */
2929 default:
2930 assert(0);
2931 }
2932 mkWinChildDelete(pChild);
2933
2934#ifdef KMK
2935 /* Flush the volatile directory cache. */
2936 dir_cache_invalid_after_job();
2937#endif
2938 return 0;
2939}
2940
2941/**
2942 * Get the child completed event handle.
2943 *
2944 * Needed when w32os.c is waiting for a job token to become available, given
2945 * that completed children is the typical source of these tokens (esp. for kmk).
2946 *
2947 * @returns Zero if completed children, event handle if waiting is required.
2948 */
2949intptr_t MkWinChildGetCompleteEventHandle(void)
2950{
2951 /* We don't return the handle if we've got completed children. This
2952 is a safe guard against being called twice in a row without any
2953 MkWinChildWait call inbetween. */
2954 if (!g_pTailCompletedChildren)
2955 return (intptr_t)g_hEvtWaitChildren;
2956 return 0;
2957}
2958
2959/**
2960 * Emulate execv() for restarting kmk after one ore more makefiles has been
2961 * made.
2962 *
2963 * Does not return.
2964 *
2965 * @param papszArgs The arguments.
2966 * @param papszEnv The environment.
2967 */
2968void MkWinChildReExecMake(char **papszArgs, char **papszEnv)
2969{
2970 PROCESS_INFORMATION ProcInfo;
2971 STARTUPINFOW StartupInfo;
2972 WCHAR *pwszCommandLine;
2973 WCHAR *pwszzEnvironment;
2974 WCHAR *pwszPathIgnored;
2975 int rc;
2976
2977 /*
2978 * Get the executable name.
2979 */
2980 WCHAR wszImageName[MKWINCHILD_MAX_PATH];
2981 DWORD cwcImageName = GetModuleFileNameW(GetModuleHandle(NULL), wszImageName, MKWINCHILD_MAX_PATH);
2982 if (cwcImageName == 0)
2983 ON(fatal, NILF, _("MkWinChildReExecMake: GetModuleFileName failed: %u\n"), GetLastError());
2984
2985 /*
2986 * Create the command line and environment.
2987 */
2988 rc = mkWinChildcareWorkerConvertCommandline(papszArgs, 0 /*fFlags*/, &pwszCommandLine);
2989 if (rc != 0)
2990 ON(fatal, NILF, _("MkWinChildReExecMake: mkWinChildcareWorkerConvertCommandline failed: %u\n"), rc);
2991
2992 rc = mkWinChildcareWorkerConvertEnvironment(papszEnv ? papszEnv : environ, 0 /*cbEnvStrings*/,
2993 &pwszzEnvironment, &pwszPathIgnored);
2994 if (rc != 0)
2995 ON(fatal, NILF, _("MkWinChildReExecMake: mkWinChildcareWorkerConvertEnvironment failed: %u\n"), rc);
2996
2997
2998 /*
2999 * Fill out the startup info and try create the process.
3000 */
3001 memset(&ProcInfo, 0, sizeof(ProcInfo));
3002 memset(&StartupInfo, 0, sizeof(StartupInfo));
3003 StartupInfo.cb = sizeof(StartupInfo);
3004 GetStartupInfoW(&StartupInfo);
3005 if (!CreateProcessW(wszImageName, pwszCommandLine, NULL /*pProcSecAttr*/, NULL /*pThreadSecAttr*/,
3006 TRUE /*fInheritHandles*/, CREATE_UNICODE_ENVIRONMENT, pwszzEnvironment, NULL /*pwsz*/,
3007 &StartupInfo, &ProcInfo))
3008 ON(fatal, NILF, _("MkWinChildReExecMake: CreateProcessW failed: %u\n"), GetLastError());
3009 CloseHandle(ProcInfo.hThread);
3010
3011 /*
3012 * Wait for it to complete and forward the status code to our parent.
3013 */
3014 for (;;)
3015 {
3016 DWORD dwExitCode = -2222;
3017 DWORD dwStatus = WaitForSingleObject(ProcInfo.hProcess, INFINITE);
3018 if ( dwStatus == WAIT_IO_COMPLETION
3019 || dwStatus == WAIT_TIMEOUT /* whatever */)
3020 continue; /* however unlikely, these aren't fatal. */
3021
3022 /* Get the status code and terminate. */
3023 if (dwStatus == WAIT_OBJECT_0)
3024 {
3025 if (!GetExitCodeProcess(ProcInfo.hProcess, &dwExitCode))
3026 {
3027 ON(fatal, NILF, _("MkWinChildReExecMake: GetExitCodeProcess failed: %u\n"), GetLastError());
3028 dwExitCode = -2222;
3029 }
3030 }
3031 else if (dwStatus)
3032 dwExitCode = dwStatus;
3033
3034 CloseHandle(ProcInfo.hProcess);
3035 for (;;)
3036 exit(dwExitCode);
3037 }
3038}
3039
3040#ifdef WITH_RW_LOCK
3041/** Serialization with kmkbuiltin_redirect. */
3042void MkWinChildExclusiveAcquire(void)
3043{
3044 AcquireSRWLockExclusive(&g_RWLock);
3045}
3046
3047/** Serialization with kmkbuiltin_redirect. */
3048void MkWinChildExclusiveRelease(void)
3049{
3050 ReleaseSRWLockExclusive(&g_RWLock);
3051}
3052#endif /* WITH_RW_LOCK */
3053
3054/**
3055 * Implementation of the CLOSE_ON_EXEC macro.
3056 *
3057 * @returns errno value.
3058 * @param fd The file descriptor to hide from children.
3059 */
3060int MkWinChildUnrelatedCloseOnExec(int fd)
3061{
3062 if (fd >= 0)
3063 {
3064 HANDLE hNative = (HANDLE)_get_osfhandle(fd);
3065 if (hNative != INVALID_HANDLE_VALUE && hNative != NULL)
3066 {
3067 if (SetHandleInformation(hNative, HANDLE_FLAG_INHERIT /*clear*/ , 0 /*set*/))
3068 return 0;
3069 }
3070 return errno;
3071 }
3072 return EINVAL;
3073}
3074
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