VirtualBox

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

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

kmkbultin: environment fixes and stats.

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

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