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

Last change on this file since 3194 was 3192, checked in by bird, 8 years ago

kmkbuiltin: funnel output thru output.c (usually via err.c).

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 110.2 KB
Line 
1/* $Id: winchildren.c 3192 2018-03-26 20:25:56Z bird $ */
2/** @file
3 * Child process creation and management for kmk.
4 */
5
6/*
7 * Copyright (c) 2018 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
8 *
9 * This file is part of kBuild.
10 *
11 * kBuild is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 3 of the License, or
14 * (at your option) any later version.
15 *
16 * kBuild is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with kBuild. If not, see <http://www.gnu.org/licenses/>
23 *
24 */
25
26/* No GNU coding style here atm, convert if upstreamed. */
27
28/** @page pg_win_children Windows child process creation and managment
29 *
30 * This new implementation aims at addressing the following:
31 *
32 * 1. Speed up process creation by doing the expensive CreateProcess call
33 * in a worker thread.
34 *
35 * 2. No 64 process limit imposed by WaitForMultipleObjects.
36 *
37 * 3. Better distribute jobs among processor groups.
38 *
39 * 4. Offloading more expensive kmkbuiltin operations to worker threads,
40 * making the main thread focus on managing child processes.
41 *
42 * 5. Output synchronization using reusable pipes.
43 *
44 *
45 * To be quite honest, the first item (CreateProcess expense) didn't occur to me
46 * at first and was more of a sideeffect discovered along the way. A test
47 * rebuilding IPRT went from 4m52s to 3m19s on a 8 thread system.
48 *
49 * The 2nd and 3rd goals are related to newer build servers that have lots of
50 * CPU threads and various Windows NT (aka NT OS/2 at the time) design choices
51 * made in the late 1980ies.
52 *
53 * WaitForMultipleObjects does not support waiting for more than 64 objects,
54 * unlike poll and select. This is just something everyone ends up having to
55 * work around in the end.
56 *
57 * Affinity masks are uintptr_t sized, so 64-bit hosts can only manage 64
58 * processors and 32-bit only 32. Workaround was introduced with Windows 7
59 * (IIRC) and is called processor groups. The CPU threads are grouped into 1 or
60 * more groups of up to 64 processors. Processes are generally scheduled to a
61 * signle processor group at first, but threads may be changed to be scheduled
62 * on different groups. This code will try distribute children evenly among the
63 * processor groups, using a very simple algorithm (see details in code).
64 *
65 *
66 * @section sec_win_children_av Remarks on Microsoft Defender and other AV
67 *
68 * Part of the motivation for writing this code was horrible CPU utilization on
69 * a brand new AMD Threadripper 1950X system with lots of memory and SSDs,
70 * running 64-bit Windows 10 build 16299.
71 *
72 * Turns out Microsoft defender adds some overhead to CreateProcess
73 * and other stuff:
74 * - Old make with CreateProcess on main thread:
75 * - With runtime defender enabled: 14 min 6 seconds
76 * - With runtime defender disabled: 4 min 49 seconds
77 * - New make with CreateProcess on worker thread (this code):
78 * - With runtime defender enabled: 6 min 29 seconds
79 * - With runtime defender disabled: 4 min 36 seconds
80 * - With runtime defender disabled out dir only: 5 min 59 seconds
81 *
82 * See also kWorker / kSubmit for more bickering about AV & disk encryption.
83 */
84
85
86/*********************************************************************************************************************************
87* Header Files *
88*********************************************************************************************************************************/
89#include "../makeint.h"
90#include "../job.h"
91#include "../filedef.h"
92#include "../debug.h"
93#include "../kmkbuiltin.h"
94#include "winchildren.h"
95
96#include <Windows.h>
97#include <Winternl.h>
98#include <assert.h>
99#include <process.h>
100#include <intrin.h>
101
102#include "nt/nt_child_inject_standard_handles.h"
103
104
105/*********************************************************************************************************************************
106* Defined Constants And Macros *
107*********************************************************************************************************************************/
108#define MKWINCHILD_MAX_PATH 1024
109
110/** Checks the UTF-16 environment variable pointed to is the PATH. */
111#define IS_PATH_ENV_VAR(a_cwcVar, a_pwszVar) \
112 ( (a_cwcVar) >= 5 \
113 && (a_pwszVar)[4] == L'=' \
114 && ((a_pwszVar)[0] == L'P' || (a_pwszVar)[0] == L'p') \
115 && ((a_pwszVar)[1] == L'A' || (a_pwszVar)[1] == L'a') \
116 && ((a_pwszVar)[2] == L'T' || (a_pwszVar)[2] == L't') \
117 && ((a_pwszVar)[3] == L'H' || (a_pwszVar)[3] == L'h') )
118
119
120/*********************************************************************************************************************************
121* Structures and Typedefs *
122*********************************************************************************************************************************/
123/**
124 * Child process type.
125 */
126typedef enum WINCHILDTYPE
127{
128 WINCHILDTYPE_INVALID = 0,
129 /** Normal child process. */
130 WINCHILDTYPE_PROCESS,
131#ifdef KMK
132 /** kmkbuiltin command. */
133 WINCHILDTYPE_BUILT_IN,
134 /** kmkbuiltin_append result write out. */
135 WINCHILDTYPE_APPEND,
136 /** kSubmit job. */
137 WINCHILDTYPE_SUBMIT,
138 /** kmk_redirect job. */
139 WINCHILDTYPE_REDIRECT,
140#endif
141 /** End of valid child types. */
142 WINCHILDTYPE_END
143} WINCHILDTYPE;
144
145
146/** Pointer to a windows child process. */
147typedef struct WINCHILD *PWINCHILD;
148/**
149 * Windows child process.
150 */
151typedef struct WINCHILD
152{
153 /** Magic / eyecatcher (WINCHILD_MAGIC). */
154 ULONG uMagic;
155 /** Child type. */
156 WINCHILDTYPE enmType;
157 /** Pointer to the next child process. */
158 PWINCHILD pNext;
159 /** The pid for this child. */
160 pid_t pid;
161 /** The make child structure associated with this child. */
162 struct child *pMkChild;
163
164 /** The process exit code. */
165 int iExitCode;
166 /** Kill signal, in case we or someone else killed it. */
167 int iSignal;
168 /** Set if core was dumped. */
169 int fCoreDumped;
170
171 /** Type specific data. */
172 union
173 {
174 /** Data for WINCHILDTYPE_PROCESS. */
175 struct
176 {
177 /** Argument vector (single allocation, strings following array). */
178 char **papszArgs;
179 /** Length of the argument strings. */
180 size_t cbArgsStrings;
181 /** Environment vector. Only a copy if fEnvIsCopy is set. */
182 char **papszEnv;
183 /** If we made a copy of the environment, this is the size of the
184 * strings and terminator string (not in array). This is done to
185 * speed up conversion, since MultiByteToWideChar can handle '\0'. */
186 size_t cbEnvStrings;
187 /** The make shell to use (copy). */
188 char *pszShell;
189 /** Handle to use for standard out. */
190 HANDLE hStdOut;
191 /** Handle to use for standard out. */
192 HANDLE hStdErr;
193 /** Whether to close hStdOut after creating the process. */
194 BOOL fCloseStdOut;
195 /** Whether to close hStdErr after creating the process. */
196 BOOL fCloseStdErr;
197 /** Whether to catch output from the process. */
198 BOOL fCatchOutput;
199
200 /** Child process handle. */
201 HANDLE hProcess;
202 } Process;
203
204 /** Data for WINCHILDTYPE_BUILT_IN. */
205 struct
206 {
207 /** The built-in command. */
208 PCKMKBUILTINENTRY pBuiltIn;
209 /** Number of arguments. */
210 int cArgs;
211 /** Argument vector (single allocation, strings following array). */
212 char **papszArgs;
213 /** Environment vector. Only a copy if fEnvIsCopy is set. */
214 char **papszEnv;
215 } BuiltIn;
216
217 /** Data for WINCHILDTYPE_APPEND. */
218 struct
219 {
220 /** The filename. */
221 char *pszFilename;
222 /** How much to append. */
223 size_t cbAppend;
224 /** What to append. */
225 char *pszAppend;
226 /** Whether to truncate the file. */
227 int fTruncate;
228 } Append;
229
230 /** Data for WINCHILDTYPE_SUBMIT. */
231 struct
232 {
233 /** The event we're to wait on (hooked up to a pipe) */
234 HANDLE hEvent;
235 /** Parameter for the cleanup callback. */
236 void *pvSubmitWorker;
237 } Submit;
238
239 /** Data for WINCHILDTYPE_REDIRECT. */
240 struct
241 {
242 /** Child process handle. */
243 HANDLE hProcess;
244 } Redirect;
245 } u;
246
247} WINCHILD;
248/** WINCHILD::uMagic value. */
249#define WINCHILD_MAGIC 0xbabebabeU
250
251/**
252 * A childcare worker pipe.
253 */
254typedef struct WINCCWPIPE
255{
256 /** My end of the pipe. */
257 HANDLE hPipeMine;
258 /** The child end of the pipe. */
259 HANDLE hPipeChild;
260 /** The event for asynchronous reading. */
261 HANDLE hEvent;
262 /** Which pipe this is (1 == stdout, 2 == stderr). */
263 unsigned char iWhich;
264 /** Set if we've got a read pending already. */
265 BOOL fReadPending;
266 /** Number of bytes at the start of the buffer that we've already
267 * written out. We try write out whole lines. */
268 DWORD cbWritten;
269 /** The buffer offset of the read currently pending. */
270 DWORD offPendingRead;
271 /** Read buffer size. */
272 DWORD cbBuffer;
273 /** The read buffer allocation. */
274 unsigned char *pbBuffer;
275 /** Overlapped I/O structure. */
276 OVERLAPPED Overlapped;
277} WINCCWPIPE;
278typedef WINCCWPIPE *PWINCCWPIPE;
279
280
281/**
282 * Data for a windows childcare worker thread.
283 *
284 * We use one worker thread per child, reusing the threads when possible.
285 *
286 * This setup helps avoid the 64-bit handle with the WaitForMultipleObject API.
287 *
288 * It also helps using all CPUs on systems with more than one CPU group
289 * (typically systems with more than 64 CPU threads or/and multiple sockets, or
290 * special configs).
291 *
292 * This helps facilitates using pipes for collecting output child rather
293 * than temporary files. Pipes doesn't involve NTFS and can easily be reused.
294 *
295 * Finally, kBuild specific, this allows running kmkbuiltin_xxxx commands in
296 * threads.
297 */
298typedef struct WINCHILDCAREWORKER
299{
300 /** Magic / eyecatcher (WINCHILDCAREWORKER_MAGIC). */
301 ULONG uMagic;
302 /** The worker index. */
303 unsigned int idxWorker;
304 /** The processor group for this worker. */
305 unsigned int iProcessorGroup;
306 /** The thread ID. */
307 unsigned int tid;
308 /** The thread handle. */
309 HANDLE hThread;
310 /** The event the thread is idling on. */
311 HANDLE hEvtIdle;
312 /** The pipe catching standard output from a child. */
313 WINCCWPIPE StdOut;
314 /** The pipe catching standard error from a child. */
315 WINCCWPIPE StdErr;
316
317 /** Pointer to the current child. */
318 PWINCHILD volatile pCurChild;
319 /** List of children pending execution on this worker.
320 * This is updated atomitically just like g_pTailCompletedChildren. */
321 PWINCHILD volatile pTailTodoChildren;
322 /** TRUE if idle, FALSE if not. */
323 long volatile fIdle;
324} WINCHILDCAREWORKER;
325/** Pointer to a childcare worker thread. */
326typedef WINCHILDCAREWORKER *PWINCHILDCAREWORKER;
327/** WINCHILD::uMagic value. */
328#define WINCHILDCAREWORKER_MAGIC 0xdad0dad0U
329
330
331/*********************************************************************************************************************************
332* Global Variables *
333*********************************************************************************************************************************/
334/** Whether it's initialized or not. */
335static BOOL g_fInitialized = FALSE;
336/** Set when we're shutting down everything. */
337static BOOL volatile g_fShutdown = FALSE;
338/** Event used to wait for children. */
339static HANDLE g_hEvtWaitChildren = INVALID_HANDLE_VALUE;
340/** Number of childcare workers currently in g_papChildCareworkers. */
341static unsigned g_cChildCareworkers = 0;
342/** Maximum number of childcare workers in g_papChildCareworkers. */
343static unsigned g_cChildCareworkersMax = 0;
344/** Pointer to childcare workers. */
345static PWINCHILDCAREWORKER *g_papChildCareworkers = NULL;
346/** The group index for the worker allocator.
347 * This is ever increasing and must be modded by g_cProcessorGroups. */
348static unsigned g_idxProcessorGroupAllocator = 0;
349/** The processor in group index for the worker allocator. */
350static unsigned g_idxProcessorInGroupAllocator = 0;
351/** Number of processor groups in the system. */
352static unsigned g_cProcessorGroups = 1;
353/** Array detailing how many active processors there are in each group. */
354static unsigned const *g_pacProcessorsInGroup = &g_cProcessorGroups;
355/** Kernel32!GetActiveProcessorGroupCount */
356static WORD (WINAPI *g_pfnGetActiveProcessorGroupCount)(VOID);
357/** Kernel32!GetActiveProcessorCount */
358static DWORD (WINAPI *g_pfnGetActiveProcessorCount)(WORD);
359/** Kernel32!SetThreadGroupAffinity */
360static BOOL (WINAPI *g_pfnSetThreadGroupAffinity)(HANDLE, CONST GROUP_AFFINITY *, GROUP_AFFINITY *);
361/** NTDLL!NtQueryInformationProcess */
362static NTSTATUS (NTAPI *g_pfnNtQueryInformationProcess)(HANDLE, PROCESSINFOCLASS, PVOID, ULONG, PULONG);
363/** Set if the windows host is 64-bit. */
364static BOOL g_f64BitHost = (K_ARCH_BITS == 64);
365/** Windows version info.
366 * @note Putting this before the volatile stuff, hoping to keep it in a
367 * different cache line than the static bits above. */
368static OSVERSIONINFOA g_VersionInfo = { sizeof(g_VersionInfo), 4, 0, 1381, VER_PLATFORM_WIN32_NT, {0} };
369
370/** Children that has been completed.
371 * This is updated atomically, pushing completed children in LIFO fashion
372 * (thus 'tail'), then hitting g_hEvtWaitChildren if head. */
373static PWINCHILD volatile g_pTailCompletedChildren = NULL;
374
375/** Number of idle pending children.
376 * This is updated before g_hEvtWaitChildren is signalled. */
377static unsigned volatile g_cPendingChildren = 0;
378
379/** Number of idle childcare worker threads. */
380static unsigned volatile g_cIdleChildcareWorkers = 0;
381/** Index of the last idle child careworker (just a hint). */
382static unsigned volatile g_idxLastChildcareWorker = 0;
383
384#ifdef WITH_RW_LOCK
385/** RW lock for serializing kmkbuiltin_redirect and CreateProcess. */
386static SRWLOCK g_RWLock;
387#endif
388
389
390#if K_ARCH_BITS == 32 && !defined(_InterlockedCompareExchangePointer)
391/** _InterlockedCompareExchangePointer is missing? (VS2010) */
392K_INLINE void *_InterlockedCompareExchangePointer(void * volatile *ppvDst, void *pvNew, void *pvOld)
393{
394 return (void *)_InterlockedCompareExchange((long volatile *)ppvDst, (intptr_t)pvNew, (intptr_t)pvOld);
395}
396#endif
397
398
399/**
400 * Initializes the windows child module.
401 *
402 * @param cJobSlots The number of job slots.
403 */
404void MkWinChildInit(unsigned int cJobSlots)
405{
406 HMODULE hmod;
407
408 /*
409 * Figure out how many childcare workers first.
410 */
411 static unsigned int const s_cMaxWorkers = 4096;
412 unsigned cWorkers;
413 if (cJobSlots >= 1 && cJobSlots < s_cMaxWorkers)
414 cWorkers = cJobSlots;
415 else
416 cWorkers = s_cMaxWorkers;
417
418 /*
419 * Allocate the array and the child completed event object.
420 */
421 g_papChildCareworkers = (PWINCHILDCAREWORKER *)xcalloc(cWorkers * sizeof(g_papChildCareworkers[0]));
422 g_cChildCareworkersMax = cWorkers;
423
424 g_hEvtWaitChildren = CreateEvent(NULL, FALSE /*fManualReset*/, FALSE /*fInitialState*/, NULL /*pszName*/);
425 if (!g_hEvtWaitChildren)
426 fatal(NILF, INTSTR_LENGTH, _("MkWinChildInit: CreateEvent failed: %u"), GetLastError());
427
428 /*
429 * NTDLL imports that we need.
430 */
431 hmod = GetModuleHandleA("NTDLL.DLL");
432 *(FARPROC *)&g_pfnNtQueryInformationProcess = GetProcAddress(hmod, "NtQueryInformationProcess");
433 if (!g_pfnNtQueryInformationProcess)
434 fatal(NILF, 0, _("MkWinChildInit: NtQueryInformationProcess not found"));
435
436#if K_ARCH_BITS == 32
437 /*
438 * Initialize g_f64BitHost.
439 */
440 if (!IsWow64Process(GetCurrentProcess(), &g_f64BitHost))
441 fatal(NILF, INTSTR_LENGTH, _("MkWinChildInit: IsWow64Process failed: %u"), GetLastError());
442#elif K_ARCH_BITS == 64
443 assert(g_f64BitHost);
444#else
445# error "K_ARCH_BITS is bad/missing"
446#endif
447
448 /*
449 * Figure out how many processor groups there are.
450 * For that we need to first figure the windows version.
451 */
452 if (!GetVersionExA(&g_VersionInfo))
453 {
454 DWORD uRawVer = GetVersion();
455 g_VersionInfo.dwMajorVersion = uRawVer & 0xff;
456 g_VersionInfo.dwMinorVersion = (uRawVer >> 8) & 0xff;
457 g_VersionInfo.dwBuildNumber = (uRawVer >> 16) & 0x7fff;
458 }
459 if (g_VersionInfo.dwMajorVersion >= 6)
460 {
461 hmod = GetModuleHandleA("KERNEL32.DLL");
462 *(FARPROC *)&g_pfnGetActiveProcessorGroupCount = GetProcAddress(hmod, "GetActiveProcessorGroupCount");
463 *(FARPROC *)&g_pfnGetActiveProcessorCount = GetProcAddress(hmod, "GetActiveProcessorCount");
464 *(FARPROC *)&g_pfnSetThreadGroupAffinity = GetProcAddress(hmod, "SetThreadGroupAffinity");
465 if ( g_pfnSetThreadGroupAffinity
466 && g_pfnGetActiveProcessorCount
467 && g_pfnGetActiveProcessorGroupCount)
468 {
469 unsigned int *pacProcessorsInGroup;
470 unsigned iGroup;
471 g_cProcessorGroups = g_pfnGetActiveProcessorGroupCount();
472 if (g_cProcessorGroups == 0)
473 g_cProcessorGroups = 1;
474
475 pacProcessorsInGroup = (unsigned int *)xmalloc(sizeof(g_pacProcessorsInGroup[0]) * g_cProcessorGroups);
476 g_pacProcessorsInGroup = pacProcessorsInGroup;
477 for (iGroup = 0; iGroup < g_cProcessorGroups; iGroup++)
478 pacProcessorsInGroup[iGroup] = g_pfnGetActiveProcessorCount(iGroup);
479
480 /* We shift the starting group with the make nesting level as part of
481 our very simple distribution strategy. */
482 g_idxProcessorGroupAllocator = makelevel;
483 }
484 else
485 {
486 g_pfnSetThreadGroupAffinity = NULL;
487 g_pfnGetActiveProcessorCount = NULL;
488 g_pfnGetActiveProcessorGroupCount = NULL;
489 }
490 }
491
492#ifdef WITH_RW_LOCK
493 /*
494 * For serializing with standard file handle manipulation (kmkbuiltin_redirect).
495 */
496 InitializeSRWLock(&g_RWLock);
497#endif
498
499 /*
500 * This is dead code that was thought to fix a problem observed doing
501 * `tcc.exe /c "kmk |& tee bld.log"` and leading to a crash in cl.exe
502 * when spawned with fInheritHandles = FALSE, see hStdErr=NULL in the
503 * child. However, it turns out this was probably caused by not clearing
504 * the CRT file descriptor and handle table in the startup info.
505 * Leaving the code here in case it comes in handy after all.
506 */
507#if 0
508 {
509 struct
510 {
511 DWORD uStdHandle;
512 HANDLE hHandle;
513 } aHandles[3] = { { STD_INPUT_HANDLE, NULL }, { STD_OUTPUT_HANDLE, NULL }, { STD_ERROR_HANDLE, NULL } };
514 int i;
515
516 for (i = 0; i < 3; i++)
517 aHandles[i].hHandle = GetStdHandle(aHandles[i].uStdHandle);
518
519 for (i = 0; i < 3; i++)
520 if ( aHandles[i].hHandle == NULL
521 || aHandles[i].hHandle == INVALID_HANDLE_VALUE)
522 {
523 int fd = open("nul", _O_RDWR);
524 if (fd >= 0)
525 {
526 if (_dup2(fd, i) >= 0)
527 {
528 assert((HANDLE)_get_osfhandle(i) != aHandles[i].hHandle);
529 assert((HANDLE)_get_osfhandle(i) == GetStdHandle(aHandles[i].uStdHandle));
530 }
531 else
532 ONNNS(fatal, NILF, "_dup2(%d('nul'), %d) failed: %u (%s)", fd, i, errno, strerror(errno));
533 if (fd != i)
534 close(fd);
535 }
536 else
537 ONNS(fatal, NILF, "open(nul,RW) failed: %u (%s)", i, errno, strerror(errno));
538 }
539 else
540 {
541 int j;
542 for (j = i + 1; j < 3; j++)
543 if (aHandles[j].hHandle == aHandles[i].hHandle)
544 {
545 int fd = _dup(j);
546 if (fd >= 0)
547 {
548 if (_dup2(fd, j) >= 0)
549 {
550 aHandles[j].hHandle = (HANDLE)_get_osfhandle(j);
551 assert(aHandles[j].hHandle != aHandles[i].hHandle);
552 assert(aHandles[j].hHandle == GetStdHandle(aHandles[j].uStdHandle));
553 }
554 else
555 ONNNS(fatal, NILF, "_dup2(%d, %d) failed: %u (%s)", fd, j, errno, strerror(errno));
556 if (fd != j)
557 close(fd);
558 }
559 else
560 ONNS(fatal, NILF, "_dup(%d) failed: %u (%s)", j, errno, strerror(errno));
561 }
562 }
563 }
564#endif
565}
566
567/**
568 * Used by mkWinChildcareWorkerThread() and MkWinChildWait() to get the head
569 * child from a lifo (g_pTailCompletedChildren, pTailTodoChildren).
570 *
571 * @returns Head child.
572 * @param ppTail Pointer to the child variable.
573 * @param pChild Tail child.
574 */
575static PWINCHILD mkWinChildDequeFromLifo(PWINCHILD volatile *ppTail, PWINCHILD pChild)
576{
577 if (pChild->pNext)
578 {
579 PWINCHILD pPrev;
580 do
581 {
582 pPrev = pChild;
583 pChild = pChild->pNext;
584 } while (pChild->pNext);
585 pPrev->pNext = NULL;
586 }
587 else
588 {
589 PWINCHILD const pWantedChild = pChild;
590 pChild = _InterlockedCompareExchangePointer(ppTail, NULL, pWantedChild);
591 if (pChild != pWantedChild)
592 {
593 PWINCHILD pPrev;
594 do
595 {
596 pPrev = pChild;
597 pChild = pChild->pNext;
598 } while (pChild->pNext);
599 pPrev->pNext = NULL;
600 assert(pChild == pWantedChild);
601 }
602 }
603 return pChild;
604}
605
606/**
607 * Duplicates the given UTF-16 string.
608 *
609 * @returns 0
610 * @param pwszSrc The UTF-16 string to duplicate.
611 * @param cwcSrc Length, may include the terminator.
612 * @param ppwszDst Where to return the duplicate.
613 */
614static int mkWinChildDuplicateUtf16String(const WCHAR *pwszSrc, size_t cwcSrc, WCHAR **ppwszDst)
615{
616 size_t cb = sizeof(WCHAR) * cwcSrc;
617 if (cwcSrc > 0 && pwszSrc[cwcSrc - 1] == L'\0')
618 *ppwszDst = (WCHAR *)memcpy(xmalloc(cb), pwszSrc, cb);
619 else
620 {
621 WCHAR *pwszDst = (WCHAR *)xmalloc(cb + sizeof(WCHAR));
622 memcpy(pwszDst, pwszSrc, cb);
623 pwszDst[cwcSrc] = L'\0';
624 *ppwszDst = pwszDst;
625 }
626 return 0;
627}
628
629
630/**
631 * Used to flush data we're read but not yet written at the termination of a
632 * process.
633 *
634 * @param pChild The child.
635 * @param pPipe The pipe.
636 */
637static void mkWinChildcareWorkerFlushUnwritten(PWINCHILD pChild, PWINCCWPIPE pPipe)
638{
639 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 KMKBUILTINCTX Ctx = { pBuiltIn->uName.s.sz, pChild->pMkChild ? &pChild->pMkChild->output : NULL };
1961 if (pBuiltIn->uFnSignature == FN_SIG_MAIN)
1962 pChild->iExitCode = pBuiltIn->u.pfnMain(pChild->u.BuiltIn.cArgs, pChild->u.BuiltIn.papszArgs,
1963 pChild->u.BuiltIn.papszEnv, &Ctx);
1964 else if (pBuiltIn->uFnSignature == FN_SIG_MAIN_SPAWNS)
1965 pChild->iExitCode = pBuiltIn->u.pfnMainSpawns(pChild->u.BuiltIn.cArgs, pChild->u.BuiltIn.papszArgs,
1966 pChild->u.BuiltIn.papszEnv, &Ctx, pChild->pMkChild, NULL);
1967 else
1968 {
1969 assert(0);
1970 pChild->iExitCode = 98;
1971 }
1972}
1973
1974/**
1975 * Childcare worker: handle append write-out.
1976 *
1977 * @param pWorker The worker.
1978 * @param pChild The kSubmit child.
1979 */
1980static void mkWinChildcareWorkerThreadHandleAppend(PWINCHILDCAREWORKER pWorker, PWINCHILD pChild)
1981{
1982 int fd = open(pChild->u.Append.pszFilename,
1983 pChild->u.Append.fTruncate
1984 ? O_WRONLY | O_TRUNC | O_CREAT | _O_NOINHERIT | _O_BINARY
1985 : O_WRONLY | O_APPEND | O_CREAT | _O_NOINHERIT | _O_BINARY,
1986 0666);
1987 if (fd >= 0)
1988 {
1989 ssize_t cbWritten = write(fd, pChild->u.Append.pszAppend, pChild->u.Append.cbAppend);
1990 if (cbWritten == (ssize_t)pChild->u.Append.cbAppend)
1991 {
1992 if (close(fd) >= 0)
1993 {
1994 pChild->iExitCode = 0;
1995 return;
1996 }
1997 fprintf(stderr, "kmk_builtin_append: close failed on '%s': %u (%s)\n",
1998 pChild->u.Append.pszFilename, errno, strerror(errno));
1999 }
2000 else
2001 fprintf(stderr, "kmk_builtin_append: error writing %lu bytes to on '%s': %u (%s)\n",
2002 pChild->u.Append.cbAppend, pChild->u.Append.pszFilename, errno, strerror(errno));
2003 close(fd);
2004 }
2005 else
2006 fprintf(stderr, "kmk_builtin_append: error opening '%s': %u (%s)\n",
2007 pChild->u.Append.pszFilename, errno, strerror(errno));
2008 pChild->iExitCode = 1;
2009}
2010
2011/**
2012 * Childcare worker: handle kSubmit job.
2013 *
2014 * @param pWorker The worker.
2015 * @param pChild The kSubmit child.
2016 */
2017static void mkWinChildcareWorkerThreadHandleSubmit(PWINCHILDCAREWORKER pWorker, PWINCHILD pChild)
2018{
2019 void *pvSubmitWorker = pChild->u.Submit.pvSubmitWorker;
2020 for (;;)
2021 {
2022 int iExitCode = -42;
2023 int iSignal = -1;
2024 DWORD dwStatus = WaitForSingleObject(pChild->u.Submit.hEvent, INFINITE);
2025 assert(dwStatus != WAIT_FAILED);
2026
2027 if (kSubmitSubProcGetResult((intptr_t)pvSubmitWorker, &iExitCode, &iSignal) == 0)
2028 {
2029 pChild->iExitCode = iExitCode;
2030 pChild->iSignal = iSignal;
2031 /* Cleanup must be done on the main thread. */
2032 return;
2033 }
2034
2035 if (pChild->iSignal != 0)
2036 kSubmitSubProcKill((intptr_t)pvSubmitWorker, pChild->iSignal);
2037 }
2038}
2039
2040/**
2041 * Childcare worker: handle kmk_redirect process.
2042 *
2043 * @param pWorker The worker.
2044 * @param pChild The redirect child.
2045 */
2046static void mkWinChildcareWorkerThreadHandleRedirect(PWINCHILDCAREWORKER pWorker, PWINCHILD pChild)
2047{
2048 mkWinChildcareWorkerWaitForProcess(pWorker, pChild, pChild->u.Redirect.hProcess, L"kmk_redirect", FALSE /*fCatchOutput*/);
2049}
2050
2051#endif /* KMK */
2052
2053/**
2054 * Childcare worker thread.
2055 *
2056 * @returns 0
2057 * @param pvUser The worker instance.
2058 */
2059static unsigned int __stdcall mkWinChildcareWorkerThread(void *pvUser)
2060{
2061 PWINCHILDCAREWORKER pWorker = (PWINCHILDCAREWORKER)pvUser;
2062 assert(pWorker->uMagic == WINCHILDCAREWORKER_MAGIC);
2063
2064 /*
2065 * Adjust process group if necessary.
2066 */
2067 if (g_cProcessorGroups > 1)
2068 {
2069 GROUP_AFFINITY Affinity = { ~(ULONG_PTR)0, pWorker->iProcessorGroup, { 0, 0, 0 } };
2070 BOOL fRet = g_pfnSetThreadGroupAffinity(GetCurrentThread(), &Affinity, NULL);
2071 assert(fRet); (void)fRet;
2072 }
2073
2074 /*
2075 * Work loop.
2076 */
2077 while (!g_fShutdown)
2078 {
2079 /*
2080 * Try go idle.
2081 */
2082 PWINCHILD pChild = pWorker->pTailTodoChildren;
2083 if (!pChild)
2084 {
2085 _InterlockedExchange(&pWorker->fIdle, TRUE);
2086 pChild = pWorker->pTailTodoChildren;
2087 if (!pChild)
2088 {
2089 DWORD dwStatus;
2090
2091 _InterlockedIncrement((long *)&g_cIdleChildcareWorkers);
2092 _InterlockedExchange((long *)&g_idxLastChildcareWorker, pWorker->idxWorker);
2093 dwStatus = WaitForSingleObject(pWorker->hEvtIdle, INFINITE);
2094 _InterlockedExchange(&pWorker->fIdle, FALSE);
2095 _InterlockedDecrement((long *)&g_cIdleChildcareWorkers);
2096
2097 assert(dwStatus != WAIT_FAILED);
2098 if (dwStatus == WAIT_FAILED)
2099 Sleep(20);
2100
2101 pChild = pWorker->pTailTodoChildren;
2102 }
2103 else
2104 _InterlockedExchange(&pWorker->fIdle, FALSE);
2105 }
2106 if (pChild)
2107 {
2108 /*
2109 * We got work to do. First job is to deque the job.
2110 */
2111 pChild = mkWinChildDequeFromLifo(&pWorker->pTailTodoChildren, pChild);
2112 assert(pChild);
2113 if (pChild)
2114 {
2115 PWINCHILD pTailExpect;
2116
2117 switch (pChild->enmType)
2118 {
2119 case WINCHILDTYPE_PROCESS:
2120 mkWinChildcareWorkerThreadHandleProcess(pWorker, pChild);
2121 break;
2122#ifdef KMK
2123 case WINCHILDTYPE_BUILT_IN:
2124 mkWinChildcareWorkerThreadHandleBuiltIn(pWorker, pChild);
2125 break;
2126 case WINCHILDTYPE_APPEND:
2127 mkWinChildcareWorkerThreadHandleAppend(pWorker, pChild);
2128 break;
2129 case WINCHILDTYPE_SUBMIT:
2130 mkWinChildcareWorkerThreadHandleSubmit(pWorker, pChild);
2131 break;
2132 case WINCHILDTYPE_REDIRECT:
2133 mkWinChildcareWorkerThreadHandleRedirect(pWorker, pChild);
2134 break;
2135#endif
2136 default:
2137 assert(0);
2138 }
2139
2140 /*
2141 * Move the child to the completed list.
2142 */
2143 pTailExpect = NULL;
2144 for (;;)
2145 {
2146 PWINCHILD pTailActual;
2147 pChild->pNext = pTailExpect;
2148 pTailActual = _InterlockedCompareExchangePointer(&g_pTailCompletedChildren, pChild, pTailExpect);
2149 if (pTailActual != pTailExpect)
2150 pTailExpect = pTailActual;
2151 else
2152 {
2153 _InterlockedDecrement(&g_cPendingChildren);
2154 if (pTailExpect)
2155 break;
2156 if (SetEvent(g_hEvtWaitChildren))
2157 break;
2158 fprintf(stderr, "SetEvent(g_hEvtWaitChildren=%p) failed: %u\n", g_hEvtWaitChildren, GetLastError());
2159 break;
2160 }
2161 }
2162 }
2163 }
2164 }
2165
2166 _endthreadex(0);
2167 return 0;
2168}
2169
2170/**
2171 * Creates a pipe for catching child output.
2172 *
2173 * This is a custom CreatePipe implementation that allows for overlapped I/O on
2174 * our end of the pipe. Silly that they don't offer an API that does this.
2175 *
2176 * @returns Success indicator.
2177 * @param pPipe The structure for the pipe.
2178 * @param iWhich Which standard descriptor this is a pipe for.
2179 * @param idxWorker The worker index.
2180 */
2181static BOOL mkWinChildcareCreateWorkerPipe(PWINCCWPIPE pPipe, unsigned iWhich, unsigned int idxWorker)
2182{
2183 /*
2184 * We try generate a reasonably unique name from the get go, so this retry
2185 * loop shouldn't really ever be needed. But you never know.
2186 */
2187 static unsigned s_iSeqNo = 0;
2188 DWORD const cMaxInstances = 1;
2189 DWORD const cbPipe = 4096;
2190 DWORD const cMsTimeout = 0;
2191 unsigned cTries = 256;
2192 while (cTries-- > 0)
2193 {
2194 /* Create the pipe (our end). */
2195 HANDLE hPipeRead;
2196 DWORD fOpenMode = PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED | FILE_FLAG_FIRST_PIPE_INSTANCE;
2197 DWORD fPipeMode = PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT | PIPE_REJECT_REMOTE_CLIENTS;
2198 WCHAR wszName[MAX_PATH];
2199 s_iSeqNo++;
2200 _snwprintf(wszName, MAX_PATH, L"\\\\.\\pipe\\kmk-winchildren-%u-%u-%u-%s-%u-%u",
2201 GetCurrentProcessId(), GetCurrentThreadId(), idxWorker, iWhich == 1 ? L"out" : L"err", s_iSeqNo, GetTickCount());
2202 hPipeRead = CreateNamedPipeW(wszName, fOpenMode, fPipeMode, cMaxInstances, cbPipe, cbPipe, cMsTimeout, NULL /*pSecAttr*/);
2203 if (hPipeRead == INVALID_HANDLE_VALUE && GetLastError() == ERROR_INVALID_PARAMETER)
2204 {
2205 fOpenMode &= ~FILE_FLAG_FIRST_PIPE_INSTANCE;
2206 fPipeMode &= ~PIPE_REJECT_REMOTE_CLIENTS;
2207 hPipeRead = CreateNamedPipeW(wszName, fOpenMode, fPipeMode, cMaxInstances, cbPipe, cbPipe, cMsTimeout, NULL /*pSecAttr*/);
2208 }
2209 if (hPipeRead != INVALID_HANDLE_VALUE)
2210 {
2211 /* Connect the other end. */
2212 HANDLE hPipeWrite = CreateFileW(wszName, GENERIC_WRITE | FILE_READ_ATTRIBUTES, 0 /*fShareMode*/, NULL /*pSecAttr*/,
2213 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL /*hTemplateFile*/);
2214 if (hPipeWrite != INVALID_HANDLE_VALUE)
2215 {
2216 /*
2217 * Create the event object and we're done.
2218 *
2219 * It starts in signalled stated so we don't need special code
2220 * for handing when we start waiting.
2221 */
2222 HANDLE hEvent = CreateEventW(NULL /*pSecAttr*/, TRUE /*fManualReset*/, TRUE /*fInitialState*/, NULL /*pwszName*/);
2223 if (hEvent != NULL)
2224 {
2225 pPipe->hPipeMine = hPipeRead;
2226 pPipe->hPipeChild = hPipeWrite;
2227 pPipe->hEvent = hEvent;
2228 pPipe->iWhich = iWhich;
2229 pPipe->fReadPending = FALSE;
2230 pPipe->cbBuffer = cbPipe;
2231 pPipe->pbBuffer = xcalloc(cbPipe);
2232 return TRUE;
2233 }
2234
2235 CloseHandle(hPipeWrite);
2236 CloseHandle(hPipeRead);
2237 return FALSE;
2238 }
2239 CloseHandle(hPipeRead);
2240 }
2241 }
2242 return FALSE;
2243}
2244
2245/**
2246 * Destroys a childcare worker pipe.
2247 *
2248 * @param pPipe The pipe.
2249 */
2250static void mkWinChildcareDeleteWorkerPipe(PWINCCWPIPE pPipe)
2251{
2252 if (pPipe->hPipeChild != NULL)
2253 {
2254 CloseHandle(pPipe->hPipeChild);
2255 pPipe->hPipeChild = NULL;
2256 }
2257
2258 if (pPipe->hPipeMine != NULL)
2259 {
2260 if (pPipe->fReadPending)
2261 if (!CancelIo(pPipe->hPipeMine))
2262 WaitForSingleObject(pPipe->hEvent, INFINITE);
2263 CloseHandle(pPipe->hPipeMine);
2264 pPipe->hPipeMine = NULL;
2265 }
2266
2267 if (pPipe->hEvent != NULL)
2268 {
2269 CloseHandle(pPipe->hEvent);
2270 pPipe->hEvent = NULL;
2271 }
2272
2273 if (pPipe->pbBuffer)
2274 {
2275 free(pPipe->pbBuffer);
2276 pPipe->pbBuffer = NULL;
2277 }
2278}
2279
2280/**
2281 * Creates another childcare worker.
2282 *
2283 * @returns The new worker, if we succeeded.
2284 */
2285static PWINCHILDCAREWORKER mkWinChildcareCreateWorker(void)
2286{
2287 PWINCHILDCAREWORKER pWorker = (PWINCHILDCAREWORKER)xcalloc(sizeof(*pWorker));
2288 pWorker->uMagic = WINCHILDCAREWORKER_MAGIC;
2289 pWorker->idxWorker = g_cChildCareworkers;
2290 pWorker->hEvtIdle = CreateEventW(NULL, FALSE /*fManualReset*/, FALSE /*fInitialState*/, NULL /*pwszName*/);
2291 if (pWorker->hEvtIdle)
2292 {
2293 if (mkWinChildcareCreateWorkerPipe(&pWorker->StdOut, 1, pWorker->idxWorker))
2294 {
2295 if (mkWinChildcareCreateWorkerPipe(&pWorker->StdErr, 2, pWorker->idxWorker))
2296 {
2297 /* Before we start the thread, assign it to a processor group. */
2298 if (g_cProcessorGroups > 1)
2299 {
2300 unsigned int cMaxInGroup;
2301 unsigned int cInGroup;
2302 unsigned int iGroup = g_idxProcessorGroupAllocator % g_cProcessorGroups;
2303 pWorker->iProcessorGroup = iGroup;
2304
2305 /* Advance. We employ a very simple strategy that does 50% in
2306 each group for each group cycle. Odd processor counts are
2307 caught in odd group cycles. The init function selects the
2308 starting group based on make nesting level to avoid stressing
2309 out the first group. */
2310 cInGroup = ++g_idxProcessorInGroupAllocator;
2311 cMaxInGroup = g_pacProcessorsInGroup[iGroup];
2312 if ( !(cMaxInGroup & 1)
2313 || !((g_idxProcessorGroupAllocator / g_cProcessorGroups) & 1))
2314 cMaxInGroup /= 2;
2315 else
2316 cMaxInGroup = cMaxInGroup / 2 + 1;
2317 if (cInGroup >= cMaxInGroup)
2318 {
2319 g_idxProcessorInGroupAllocator = 0;
2320 g_idxProcessorGroupAllocator++;
2321 }
2322 }
2323
2324 /* Try start the thread. */
2325 pWorker->hThread = (HANDLE)_beginthreadex(NULL, 0 /*cbStack*/, mkWinChildcareWorkerThread, pWorker,
2326 0, &pWorker->tid);
2327 if (pWorker->hThread != NULL)
2328 {
2329 pWorker->idxWorker = g_cChildCareworkers++; /* paranoia */
2330 g_papChildCareworkers[pWorker->idxWorker] = pWorker;
2331 return pWorker;
2332 }
2333
2334 /* Bail out! */
2335 fprintf(stderr, "warning! _beginthreadex failed: %u (%s)\n", errno, strerror(errno));
2336 mkWinChildcareDeleteWorkerPipe(&pWorker->StdErr);
2337 }
2338 else
2339 fprintf(stderr, "warning! Failed to create stderr pipe: %u\n", GetLastError());
2340 mkWinChildcareDeleteWorkerPipe(&pWorker->StdOut);
2341 }
2342 else
2343 fprintf(stderr, "warning! Failed to create stdout pipe: %u\n", GetLastError());
2344 CloseHandle(pWorker->hEvtIdle);
2345 }
2346 else
2347 fprintf(stderr, "warning! CreateEvent failed: %u\n", GetLastError());
2348 pWorker->uMagic = ~WINCHILDCAREWORKER_MAGIC;
2349 free(pWorker);
2350 return NULL;
2351}
2352
2353/**
2354 * Helper for copying argument and environment vectors.
2355 *
2356 * @returns Single alloc block copy.
2357 * @param papszSrc The source vector.
2358 * @param pcbStrings Where to return the size of the strings & terminator.
2359 */
2360static char **mkWinChildCopyStringArray(char **papszSrc, size_t *pcbStrings)
2361{
2362 const char *psz;
2363 char **papszDstArray;
2364 char *pszDstStr;
2365 size_t i;
2366
2367 /* Calc sizes first. */
2368 size_t cbStrings = 1; /* (one extra for terminator string) */
2369 size_t cStrings = 0;
2370 while ((psz = papszSrc[cStrings]) != NULL)
2371 {
2372 cbStrings += strlen(psz) + 1;
2373 cStrings++;
2374 }
2375 *pcbStrings = cbStrings;
2376
2377 /* Allocate destination. */
2378 papszDstArray = (char **)xmalloc(cbStrings + (cStrings + 1) * sizeof(papszDstArray[0]));
2379 pszDstStr = (char *)&papszDstArray[cStrings + 1];
2380
2381 /* Copy it. */
2382 for (i = 0; i < cStrings; i++)
2383 {
2384 const char *pszSource = papszSrc[i];
2385 size_t cchString = strlen(pszSource);
2386 papszDstArray[i] = pszDstStr;
2387 memcpy(pszDstStr, pszSource, cchString);
2388 pszDstStr += cchString;
2389 *pszDstStr++ = '\0';
2390 }
2391 *pszDstStr = '\0';
2392 assert(&pszDstStr[1] - papszDstArray[0] == cbStrings);
2393 papszDstArray[i] = NULL;
2394 return papszDstArray;
2395}
2396
2397/**
2398 * Allocate and init a WINCHILD.
2399 *
2400 * @returns The new windows child structure.
2401 * @param enmType The child type.
2402 */
2403static PWINCHILD mkWinChildNew(WINCHILDTYPE enmType)
2404{
2405 PWINCHILD pChild = xcalloc(sizeof(*pChild));
2406 pChild->enmType = enmType;
2407 pChild->fCoreDumped = 0;
2408 pChild->iSignal = 0;
2409 pChild->iExitCode = 222222;
2410 pChild->uMagic = WINCHILD_MAGIC;
2411 pChild->pid = (intptr_t)pChild;
2412 return pChild;
2413}
2414
2415/**
2416 * Destructor for WINCHILD.
2417 *
2418 * @param pChild The child structure to destroy.
2419 */
2420static void mkWinChildDelete(PWINCHILD pChild)
2421{
2422 assert(pChild->uMagic == WINCHILD_MAGIC);
2423 pChild->uMagic = ~WINCHILD_MAGIC;
2424
2425 switch (pChild->enmType)
2426 {
2427 case WINCHILDTYPE_PROCESS:
2428 {
2429 if (pChild->u.Process.papszArgs)
2430 {
2431 free(pChild->u.Process.papszArgs);
2432 pChild->u.Process.papszArgs = NULL;
2433 }
2434 if (pChild->u.Process.cbEnvStrings && pChild->u.Process.papszEnv)
2435 {
2436 free(pChild->u.Process.papszEnv);
2437 pChild->u.Process.papszEnv = NULL;
2438 }
2439 if (pChild->u.Process.pszShell)
2440 {
2441 free(pChild->u.Process.pszShell);
2442 pChild->u.Process.pszShell = NULL;
2443 }
2444 if (pChild->u.Process.hProcess)
2445 {
2446 CloseHandle(pChild->u.Process.hProcess);
2447 pChild->u.Process.hProcess = NULL;
2448 }
2449 mkWinChildcareWorkerCloseStandardHandles(pChild);
2450 break;
2451 }
2452
2453#ifdef KMK
2454 case WINCHILDTYPE_BUILT_IN:
2455 if (pChild->u.BuiltIn.papszArgs)
2456 {
2457 free(pChild->u.BuiltIn.papszArgs);
2458 pChild->u.BuiltIn.papszArgs = NULL;
2459 }
2460 if (pChild->u.BuiltIn.papszEnv)
2461 {
2462 free(pChild->u.BuiltIn.papszEnv);
2463 pChild->u.BuiltIn.papszEnv = NULL;
2464 }
2465 break;
2466
2467 case WINCHILDTYPE_APPEND:
2468 if (pChild->u.Append.pszFilename)
2469 {
2470 free(pChild->u.Append.pszFilename);
2471 pChild->u.Append.pszFilename = NULL;
2472 }
2473 if (pChild->u.Append.pszAppend)
2474 {
2475 free(pChild->u.Append.pszAppend);
2476 pChild->u.Append.pszAppend = NULL;
2477 }
2478 break;
2479
2480 case WINCHILDTYPE_SUBMIT:
2481 if (pChild->u.Submit.pvSubmitWorker)
2482 {
2483 kSubmitSubProcCleanup((intptr_t)pChild->u.Submit.pvSubmitWorker);
2484 pChild->u.Submit.pvSubmitWorker = NULL;
2485 }
2486 break;
2487
2488 case WINCHILDTYPE_REDIRECT:
2489 if (pChild->u.Redirect.hProcess)
2490 {
2491 CloseHandle(pChild->u.Redirect.hProcess);
2492 pChild->u.Redirect.hProcess = NULL;
2493 }
2494 break;
2495#endif /* KMK */
2496
2497 default:
2498 assert(0);
2499 }
2500
2501 free(pChild);
2502}
2503
2504/**
2505 * Queues the child with a worker, creating new workers if necessary.
2506 *
2507 * @returns 0 on success, windows error code on failure (child destroyed).
2508 * @param pChild The child.
2509 * @param pPid Where to return the PID (optional).
2510 */
2511static int mkWinChildPushToCareWorker(PWINCHILD pChild, pid_t *pPid)
2512{
2513 PWINCHILDCAREWORKER pWorker = NULL;
2514 PWINCHILD pOldChild;
2515 PWINCHILD pCurChild;
2516
2517 /*
2518 * There are usually idle workers around, except for at the start.
2519 */
2520 if (g_cIdleChildcareWorkers > 0)
2521 {
2522 /*
2523 * Try the idle hint first and move forward from it.
2524 */
2525 unsigned int const cWorkers = g_cChildCareworkers;
2526 unsigned int iHint = g_idxLastChildcareWorker;
2527 unsigned int i;
2528 for (i = iHint; i < cWorkers; i++)
2529 {
2530 PWINCHILDCAREWORKER pPossibleWorker = g_papChildCareworkers[i];
2531 if (pPossibleWorker->fIdle)
2532 {
2533 pWorker = pPossibleWorker;
2534 break;
2535 }
2536 }
2537 if (!pWorker)
2538 {
2539 /* Scan from the start. */
2540 if (iHint > cWorkers)
2541 iHint = cWorkers;
2542 for (i = 0; i < iHint; i++)
2543 {
2544 PWINCHILDCAREWORKER pPossibleWorker = g_papChildCareworkers[i];
2545 if (pPossibleWorker->fIdle)
2546 {
2547 pWorker = pPossibleWorker;
2548 break;
2549 }
2550 }
2551 }
2552 }
2553 if (!pWorker)
2554 {
2555 /*
2556 * Try create more workers if we haven't reached the max yet.
2557 */
2558 if (g_cChildCareworkers < g_cChildCareworkersMax)
2559 pWorker = mkWinChildcareCreateWorker();
2560
2561 /*
2562 * Queue it with an existing worker. Look for one without anthing extra scheduled.
2563 */
2564 if (!pWorker)
2565 {
2566 unsigned int i = g_cChildCareworkers;
2567 if (i == 0)
2568 fatal(NILF, 0, _("Failed to create worker threads for managing child processes!\n"));
2569 pWorker = g_papChildCareworkers[--i];
2570 if (pWorker->pTailTodoChildren)
2571 while (i-- > 0)
2572 {
2573 PWINCHILDCAREWORKER pPossibleWorker = g_papChildCareworkers[i];
2574 if (!pPossibleWorker->pTailTodoChildren)
2575 {
2576 pWorker = pPossibleWorker;
2577 break;
2578 }
2579 }
2580 }
2581 }
2582
2583 /*
2584 * Do the queueing.
2585 */
2586 pOldChild = NULL;
2587 for (;;)
2588 {
2589 pChild->pNext = pOldChild;
2590 pCurChild = _InterlockedCompareExchangePointer((void **)&pWorker->pTailTodoChildren, pChild, pOldChild);
2591 if (pCurChild == pOldChild)
2592 {
2593 DWORD volatile dwErr;
2594 _InterlockedIncrement(&g_cPendingChildren);
2595 if ( !pWorker->fIdle
2596 || SetEvent(pWorker->hEvtIdle))
2597 {
2598 *pPid = pChild->pid;
2599 return 0;
2600 }
2601
2602 _InterlockedDecrement(&g_cPendingChildren);
2603 dwErr = GetLastError();
2604 assert(0);
2605 mkWinChildDelete(pChild);
2606 return dwErr ? dwErr : -20;
2607 }
2608 pOldChild = pCurChild;
2609 }
2610}
2611
2612/**
2613 * Creates a regular child process (job.c).
2614 *
2615 * Will copy the information and push it to a childcare thread that does the
2616 * actual process creation.
2617 *
2618 * @returns 0 on success, windows status code on failure.
2619 * @param papszArgs The arguments.
2620 * @param papszEnv The environment (optional).
2621 * @param pszShell The SHELL variable value (optional).
2622 * @param pMkChild The make child structure (optional).
2623 * @param pPid Where to return the pid.
2624 */
2625int MkWinChildCreate(char **papszArgs, char **papszEnv, const char *pszShell, struct child *pMkChild, pid_t *pPid)
2626{
2627 PWINCHILD pChild = mkWinChildNew(WINCHILDTYPE_PROCESS);
2628 pChild->pMkChild = pMkChild;
2629
2630 pChild->u.Process.papszArgs = mkWinChildCopyStringArray(papszArgs, &pChild->u.Process.cbArgsStrings);
2631 if ( !papszEnv
2632 || !pMkChild
2633 || pMkChild->environment == papszEnv)
2634 {
2635 pChild->u.Process.cbEnvStrings = 0;
2636 pChild->u.Process.papszEnv = papszEnv;
2637 }
2638 else
2639 pChild->u.Process.papszEnv = mkWinChildCopyStringArray(papszEnv, &pChild->u.Process.cbEnvStrings);
2640 if (pszShell)
2641 pChild->u.Process.pszShell = xstrdup(pszShell);
2642 pChild->u.Process.hStdOut = INVALID_HANDLE_VALUE;
2643 pChild->u.Process.hStdErr = INVALID_HANDLE_VALUE;
2644
2645 /* We always catch the output in order to prevent character soups courtesy
2646 of the microsoft CRT and/or linkers writing character by character to the
2647 console. Always try write whole lines, even when --output-sync is none. */
2648 pChild->u.Process.fCatchOutput = TRUE;
2649
2650 return mkWinChildPushToCareWorker(pChild, pPid);
2651}
2652
2653/**
2654 * Creates a chile process with a pipe hooked up to stdout.
2655 *
2656 * @returns 0 on success, non-zero on failure.
2657 * @param papszArgs The argument vector.
2658 * @param papszEnv The environment vector (optional).
2659 * @param fdErr File descriptor to hook up to stderr.
2660 * @param pPid Where to return the pid.
2661 * @param pfdReadPipe Where to return the read end of the pipe.
2662 */
2663int MkWinChildCreateWithStdOutPipe(char **papszArgs, char **papszEnv, int fdErr, pid_t *pPid, int *pfdReadPipe)
2664{
2665 /*
2666 * Create the pipe.
2667 */
2668 HANDLE hReadPipe;
2669 HANDLE hWritePipe;
2670 if (CreatePipe(&hReadPipe, &hWritePipe, NULL, 0 /* default size */))
2671 {
2672 if (SetHandleInformation(hWritePipe, HANDLE_FLAG_INHERIT /* clear */ , HANDLE_FLAG_INHERIT /*set*/))
2673 {
2674 int fdReadPipe = _open_osfhandle((intptr_t)hReadPipe, O_RDONLY);
2675 if (fdReadPipe >= 0)
2676 {
2677 PWINCHILD pChild;
2678 int rc;
2679
2680 /*
2681 * Get a handle for fdErr. Ignore failure.
2682 */
2683 HANDLE hStdErr = INVALID_HANDLE_VALUE;
2684 if (fdErr >= 0)
2685 {
2686 HANDLE hNative = (HANDLE)_get_osfhandle(fdErr);
2687 if (!DuplicateHandle(GetCurrentProcess(), hNative, GetCurrentProcess(),
2688 &hStdErr, 0 /*DesiredAccess*/, TRUE /*fInherit*/, DUPLICATE_SAME_ACCESS))
2689 {
2690 ONN(error, NILF, _("DuplicateHandle failed on stderr descriptor (%u): %u\n"), fdErr, GetLastError());
2691 hStdErr = INVALID_HANDLE_VALUE;
2692 }
2693 }
2694
2695 /*
2696 * Push it off to the worker thread.
2697 */
2698 pChild = mkWinChildNew(WINCHILDTYPE_PROCESS);
2699 pChild->u.Process.papszArgs = mkWinChildCopyStringArray(papszArgs, &pChild->u.Process.cbArgsStrings);
2700 pChild->u.Process.papszEnv = mkWinChildCopyStringArray(papszEnv ? papszEnv : environ,
2701 &pChild->u.Process.cbEnvStrings);
2702 //if (pszShell)
2703 // pChild->u.Process.pszShell = xstrdup(pszShell);
2704 pChild->u.Process.hStdOut = hWritePipe;
2705 pChild->u.Process.hStdErr = hStdErr;
2706 pChild->u.Process.fCloseStdErr = TRUE;
2707 pChild->u.Process.fCloseStdOut = TRUE;
2708
2709 rc = mkWinChildPushToCareWorker(pChild, pPid);
2710 if (rc == 0)
2711 *pfdReadPipe = fdReadPipe;
2712 else
2713 {
2714 ON(error, NILF, _("mkWinChildPushToCareWorker failed on pipe: %d\n"), rc);
2715 close(fdReadPipe);
2716 *pfdReadPipe = -1;
2717 *pPid = -1;
2718 }
2719 return rc;
2720 }
2721
2722 ON(error, NILF, _("_open_osfhandle failed on pipe: %u\n"), errno);
2723 }
2724 else
2725 ON(error, NILF, _("SetHandleInformation failed on pipe: %u\n"), GetLastError());
2726 if (hReadPipe != INVALID_HANDLE_VALUE)
2727 CloseHandle(hReadPipe);
2728 CloseHandle(hWritePipe);
2729 }
2730 else
2731 ON(error, NILF, _("CreatePipe failed: %u\n"), GetLastError());
2732 *pfdReadPipe = -1;
2733 *pPid = -1;
2734 return -1;
2735}
2736
2737#ifdef KMK
2738
2739/**
2740 * Interface used by kmkbuiltin.c for executing builtin commands on threads.
2741 *
2742 * @returns 0 on success, windows status code on failure.
2743 * @param pBuiltIn The kmk built-in command entry.
2744 * @param cArgs The number of arguments in papszArgs.
2745 * @param papszArgs The argument vector.
2746 * @param papszEnv The environment vector, optional.
2747 * @param pMkChild The make child structure.
2748 * @param pPid Where to return the pid.
2749 */
2750int MkWinChildCreateBuiltIn(PCKMKBUILTINENTRY pBuiltIn, int cArgs, char **papszArgs, char **papszEnv,
2751 struct child *pMkChild, pid_t *pPid)
2752{
2753 size_t cbIgnored;
2754 PWINCHILD pChild = mkWinChildNew(WINCHILDTYPE_BUILT_IN);
2755 pChild->pMkChild = pMkChild;
2756 pChild->u.BuiltIn.pBuiltIn = pBuiltIn;
2757 pChild->u.BuiltIn.cArgs = cArgs;
2758 pChild->u.BuiltIn.papszArgs = mkWinChildCopyStringArray(papszArgs, &cbIgnored);
2759 pChild->u.BuiltIn.papszEnv = papszEnv ? mkWinChildCopyStringArray(papszEnv, &cbIgnored) : NULL;
2760 return mkWinChildPushToCareWorker(pChild, pPid);
2761}
2762
2763/**
2764 * Interface used by append.c for do the slow file system part.
2765 *
2766 * This will append the given buffer to the specified file and free the buffer.
2767 *
2768 * @returns 0 on success, windows status code on failure.
2769 *
2770 * @param pszFilename The name of the file to append to.
2771 * @param ppszAppend What to append. The pointer pointed to is set to
2772 * NULL once we've taken ownership of the buffer and
2773 * promise to free it.
2774 * @param cbAppend How much to append.
2775 * @param fTruncate Whether to truncate the file before appending to it.
2776 * @param pMkChild The make child structure.
2777 * @param pPid Where to return the pid.
2778 */
2779int MkWinChildCreateAppend(const char *pszFilename, char **ppszAppend, size_t cbAppend, int fTruncate,
2780 struct child *pMkChild, pid_t *pPid)
2781{
2782 size_t cbFilename = strlen(pszFilename) + 1;
2783 PWINCHILD pChild = mkWinChildNew(WINCHILDTYPE_APPEND);
2784 pChild->pMkChild = pMkChild;
2785 pChild->u.Append.fTruncate = fTruncate;
2786 pChild->u.Append.pszAppend = *ppszAppend;
2787 pChild->u.Append.cbAppend = cbAppend;
2788 pChild->u.Append.pszFilename = (char *)memcpy(xmalloc(cbFilename), pszFilename, cbFilename);
2789 *ppszAppend = NULL;
2790 return mkWinChildPushToCareWorker(pChild, pPid);
2791}
2792
2793/**
2794 * Interface used by kSubmit.c for registering stuff to wait on.
2795 *
2796 * @returns 0 on success, windows status code on failure.
2797 * @param hEvent The event object handle to wait on.
2798 * @param pvSubmitWorker The argument to pass back to kSubmit to clean up.
2799 * @param pPid Where to return the pid.
2800 */
2801int MkWinChildCreateSubmit(intptr_t hEvent, void *pvSubmitWorker, pid_t *pPid)
2802{
2803 PWINCHILD pChild = mkWinChildNew(WINCHILDTYPE_SUBMIT);
2804 pChild->u.Submit.hEvent = (HANDLE)hEvent;
2805 pChild->u.Submit.pvSubmitWorker = pvSubmitWorker;
2806 return mkWinChildPushToCareWorker(pChild, pPid);
2807}
2808
2809/**
2810 * Interface used by redirect.c for registering stuff to wait on.
2811 *
2812 * @returns 0 on success, windows status code on failure.
2813 * @param hProcess The process object to wait on.
2814 * @param pPid Where to return the pid.
2815 */
2816int MkWinChildCreateRedirect(intptr_t hProcess, pid_t *pPid)
2817{
2818 PWINCHILD pChild = mkWinChildNew(WINCHILDTYPE_REDIRECT);
2819 pChild->u.Redirect.hProcess = (HANDLE)hProcess;
2820 return mkWinChildPushToCareWorker(pChild, pPid);
2821}
2822
2823#endif /* CONFIG_NEW_WIN_CHILDREN */
2824
2825/**
2826 * Interface used to kill process when processing Ctrl-C and fatal errors.
2827 *
2828 * @returns 0 on success, -1 & errno on error.
2829 * @param pid The process to kill (PWINCHILD).
2830 * @param iSignal What to kill it with.
2831 * @param pMkChild The make child structure for validation.
2832 */
2833int MkWinChildKill(pid_t pid, int iSignal, struct child *pMkChild)
2834{
2835 PWINCHILD pChild = (PWINCHILD)pid;
2836 if (pChild)
2837 {
2838 assert(pChild->uMagic == WINCHILD_MAGIC);
2839 if (pChild->uMagic == WINCHILD_MAGIC)
2840 {
2841 switch (pChild->enmType)
2842 {
2843 case WINCHILDTYPE_PROCESS:
2844 assert(pChild->pMkChild == pMkChild);
2845 TerminateProcess(pChild->u.Process.hProcess, DBG_TERMINATE_PROCESS);
2846 pChild->iSignal = iSignal;
2847 break;
2848#ifdef KMK
2849 case WINCHILDTYPE_SUBMIT:
2850 {
2851 pChild->iSignal = iSignal;
2852 SetEvent(pChild->u.Submit.hEvent);
2853 break;
2854 }
2855
2856 case WINCHILDTYPE_REDIRECT:
2857 TerminateProcess(pChild->u.Redirect.hProcess, DBG_TERMINATE_PROCESS);
2858 pChild->iSignal = iSignal;
2859 break;
2860
2861 case WINCHILDTYPE_BUILT_IN:
2862 break;
2863
2864#endif /* KMK */
2865 default:
2866 assert(0);
2867 }
2868 }
2869 }
2870 return -1;
2871}
2872
2873/**
2874 * Wait for a child process to complete
2875 *
2876 * @returns 0 on success, windows error code on failure.
2877 * @param fBlock Whether to block.
2878 * @param pPid Where to return the pid if a child process
2879 * completed. This is set to zero if none.
2880 * @param piExitCode Where to return the exit code.
2881 * @param piSignal Where to return the exit signal number.
2882 * @param pfCoreDumped Where to return the core dumped indicator.
2883 * @param ppMkChild Where to return the associated struct child pointer.
2884 */
2885int MkWinChildWait(int fBlock, pid_t *pPid, int *piExitCode, int *piSignal, int *pfCoreDumped, struct child **ppMkChild)
2886{
2887 PWINCHILD pChild;
2888
2889 *pPid = 0;
2890 *piExitCode = -222222;
2891 *pfCoreDumped = 0;
2892 *ppMkChild = NULL;
2893
2894 /*
2895 * Wait if necessary.
2896 */
2897 if (fBlock && !g_pTailCompletedChildren && g_cPendingChildren > 0)
2898 {
2899 DWORD dwStatus = WaitForSingleObject(g_hEvtWaitChildren, INFINITE);
2900 if (dwStatus == WAIT_FAILED)
2901 return (int)GetLastError();
2902 }
2903
2904 /*
2905 * Try unlink the last child in the LIFO.
2906 */
2907 pChild = g_pTailCompletedChildren;
2908 if (!pChild)
2909 return 0;
2910 pChild = mkWinChildDequeFromLifo(&g_pTailCompletedChildren, pChild);
2911 assert(pChild);
2912
2913 /*
2914 * Set return values and ditch the child structure.
2915 */
2916 *pPid = pChild->pid;
2917 *piExitCode = pChild->iExitCode;
2918 *pfCoreDumped = pChild->fCoreDumped;
2919 *ppMkChild = pChild->pMkChild;
2920 switch (pChild->enmType)
2921 {
2922 case WINCHILDTYPE_PROCESS:
2923 break;
2924#ifdef KMK
2925 case WINCHILDTYPE_BUILT_IN:
2926 case WINCHILDTYPE_APPEND:
2927 case WINCHILDTYPE_SUBMIT:
2928 case WINCHILDTYPE_REDIRECT:
2929 break;
2930#endif /* KMK */
2931 default:
2932 assert(0);
2933 }
2934 mkWinChildDelete(pChild);
2935
2936#ifdef KMK
2937 /* Flush the volatile directory cache. */
2938 dir_cache_invalid_after_job();
2939#endif
2940 return 0;
2941}
2942
2943/**
2944 * Get the child completed event handle.
2945 *
2946 * Needed when w32os.c is waiting for a job token to become available, given
2947 * that completed children is the typical source of these tokens (esp. for kmk).
2948 *
2949 * @returns Zero if completed children, event handle if waiting is required.
2950 */
2951intptr_t MkWinChildGetCompleteEventHandle(void)
2952{
2953 /* We don't return the handle if we've got completed children. This
2954 is a safe guard against being called twice in a row without any
2955 MkWinChildWait call inbetween. */
2956 if (!g_pTailCompletedChildren)
2957 return (intptr_t)g_hEvtWaitChildren;
2958 return 0;
2959}
2960
2961/**
2962 * Emulate execv() for restarting kmk after one ore more makefiles has been
2963 * made.
2964 *
2965 * Does not return.
2966 *
2967 * @param papszArgs The arguments.
2968 * @param papszEnv The environment.
2969 */
2970void MkWinChildReExecMake(char **papszArgs, char **papszEnv)
2971{
2972 PROCESS_INFORMATION ProcInfo;
2973 STARTUPINFOW StartupInfo;
2974 WCHAR *pwszCommandLine;
2975 WCHAR *pwszzEnvironment;
2976 WCHAR *pwszPathIgnored;
2977 int rc;
2978
2979 /*
2980 * Get the executable name.
2981 */
2982 WCHAR wszImageName[MKWINCHILD_MAX_PATH];
2983 DWORD cwcImageName = GetModuleFileNameW(GetModuleHandle(NULL), wszImageName, MKWINCHILD_MAX_PATH);
2984 if (cwcImageName == 0)
2985 ON(fatal, NILF, _("MkWinChildReExecMake: GetModuleFileName failed: %u\n"), GetLastError());
2986
2987 /*
2988 * Create the command line and environment.
2989 */
2990 rc = mkWinChildcareWorkerConvertCommandline(papszArgs, 0 /*fFlags*/, &pwszCommandLine);
2991 if (rc != 0)
2992 ON(fatal, NILF, _("MkWinChildReExecMake: mkWinChildcareWorkerConvertCommandline failed: %u\n"), rc);
2993
2994 rc = mkWinChildcareWorkerConvertEnvironment(papszEnv ? papszEnv : environ, 0 /*cbEnvStrings*/,
2995 &pwszzEnvironment, &pwszPathIgnored);
2996 if (rc != 0)
2997 ON(fatal, NILF, _("MkWinChildReExecMake: mkWinChildcareWorkerConvertEnvironment failed: %u\n"), rc);
2998
2999
3000 /*
3001 * Fill out the startup info and try create the process.
3002 */
3003 memset(&ProcInfo, 0, sizeof(ProcInfo));
3004 memset(&StartupInfo, 0, sizeof(StartupInfo));
3005 StartupInfo.cb = sizeof(StartupInfo);
3006 GetStartupInfoW(&StartupInfo);
3007 if (!CreateProcessW(wszImageName, pwszCommandLine, NULL /*pProcSecAttr*/, NULL /*pThreadSecAttr*/,
3008 TRUE /*fInheritHandles*/, CREATE_UNICODE_ENVIRONMENT, pwszzEnvironment, NULL /*pwsz*/,
3009 &StartupInfo, &ProcInfo))
3010 ON(fatal, NILF, _("MkWinChildReExecMake: CreateProcessW failed: %u\n"), GetLastError());
3011 CloseHandle(ProcInfo.hThread);
3012
3013 /*
3014 * Wait for it to complete and forward the status code to our parent.
3015 */
3016 for (;;)
3017 {
3018 DWORD dwExitCode = -2222;
3019 DWORD dwStatus = WaitForSingleObject(ProcInfo.hProcess, INFINITE);
3020 if ( dwStatus == WAIT_IO_COMPLETION
3021 || dwStatus == WAIT_TIMEOUT /* whatever */)
3022 continue; /* however unlikely, these aren't fatal. */
3023
3024 /* Get the status code and terminate. */
3025 if (dwStatus == WAIT_OBJECT_0)
3026 {
3027 if (!GetExitCodeProcess(ProcInfo.hProcess, &dwExitCode))
3028 {
3029 ON(fatal, NILF, _("MkWinChildReExecMake: GetExitCodeProcess failed: %u\n"), GetLastError());
3030 dwExitCode = -2222;
3031 }
3032 }
3033 else if (dwStatus)
3034 dwExitCode = dwStatus;
3035
3036 CloseHandle(ProcInfo.hProcess);
3037 for (;;)
3038 exit(dwExitCode);
3039 }
3040}
3041
3042#ifdef WITH_RW_LOCK
3043/** Serialization with kmkbuiltin_redirect. */
3044void MkWinChildExclusiveAcquire(void)
3045{
3046 AcquireSRWLockExclusive(&g_RWLock);
3047}
3048
3049/** Serialization with kmkbuiltin_redirect. */
3050void MkWinChildExclusiveRelease(void)
3051{
3052 ReleaseSRWLockExclusive(&g_RWLock);
3053}
3054#endif /* WITH_RW_LOCK */
3055
3056/**
3057 * Implementation of the CLOSE_ON_EXEC macro.
3058 *
3059 * @returns errno value.
3060 * @param fd The file descriptor to hide from children.
3061 */
3062int MkWinChildUnrelatedCloseOnExec(int fd)
3063{
3064 if (fd >= 0)
3065 {
3066 HANDLE hNative = (HANDLE)_get_osfhandle(fd);
3067 if (hNative != INVALID_HANDLE_VALUE && hNative != NULL)
3068 {
3069 if (SetHandleInformation(hNative, HANDLE_FLAG_INHERIT /*clear*/ , 0 /*set*/))
3070 return 0;
3071 }
3072 return errno;
3073 }
3074 return EINVAL;
3075}
3076
Note: See TracBrowser for help on using the repository browser.