VirtualBox

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

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

kmk/win: Some fixes & docs.

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