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

Last change on this file since 3359 was 3359, checked in by bird, 5 years ago

kmk,kFsCache: Added variant of kFsCacheInvalidateAll that also closes directory handles, we need to do this befor ere-executing kmk after having remake some include file we needed. It messes up fetching otherwise.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 135.5 KB
Line 
1/* $Id: winchildren.c 3359 2020-06-05 16:17:17Z bird $ */
2/** @file
3 * Child process creation and management for kmk.
4 */
5
6/*
7 * Copyright (c) 2018 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
8 *
9 * This file is part of kBuild.
10 *
11 * kBuild is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 3 of the License, or
14 * (at your option) any later version.
15 *
16 * kBuild is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with kBuild. If not, see <http://www.gnu.org/licenses/>
23 *
24 */
25
26/* No GNU coding style here atm, convert if upstreamed. */
27
28/** @page pg_win_children Windows child process creation and managment
29 *
30 * This new implementation aims at addressing the following:
31 *
32 * 1. Speed up process creation by doing the expensive CreateProcess call
33 * in a worker thread.
34 *
35 * 2. No 64 process limit imposed by WaitForMultipleObjects.
36 *
37 * 3. Better distribute jobs among processor groups.
38 *
39 * 4. Offloading more expensive kmkbuiltin operations to worker threads,
40 * making the main thread focus on managing child processes.
41 *
42 * 5. Output synchronization using reusable pipes.
43 *
44 *
45 * To be quite honest, the first item (CreateProcess expense) didn't occur to me
46 * at first and was more of a sideeffect discovered along the way. A test
47 * rebuilding IPRT went from 4m52s to 3m19s on a 8 thread system.
48 *
49 * The 2nd and 3rd goals are related to newer build servers that have lots of
50 * CPU threads and various Windows NT (aka NT OS/2 at the time) design choices
51 * made in the late 1980ies.
52 *
53 * WaitForMultipleObjects does not support waiting for more than 64 objects,
54 * unlike poll and select. This is just something everyone ends up having to
55 * work around in the end.
56 *
57 * Affinity masks are uintptr_t sized, so 64-bit hosts can only manage 64
58 * processors and 32-bit only 32. Workaround was introduced with Windows 7
59 * (IIRC) and is called processor groups. The CPU threads are grouped into 1 or
60 * more groups of up to 64 processors. Processes are generally scheduled to a
61 * signle processor group at first, but threads may be changed to be scheduled
62 * on different groups. This code will try distribute children evenly among the
63 * processor groups, using a very simple algorithm (see details in code).
64 *
65 *
66 * @section sec_win_children_av Remarks on Microsoft Defender and other AV
67 *
68 * Part of the motivation for writing this code was horrible CPU utilization on
69 * a brand new AMD Threadripper 1950X system with lots of memory and SSDs,
70 * running 64-bit Windows 10 build 16299.
71 *
72 * Turns out Microsoft defender adds some overhead to CreateProcess
73 * and other stuff:
74 * - Old make with CreateProcess on main thread:
75 * - With runtime defender enabled: 14 min 6 seconds
76 * - With runtime defender disabled: 4 min 49 seconds
77 * - New make with CreateProcess on worker thread (this code):
78 * - With runtime defender enabled: 6 min 29 seconds
79 * - With runtime defender disabled: 4 min 36 seconds
80 * - With runtime defender disabled out dir only: 5 min 59 seconds
81 *
82 * See also kWorker / kSubmit for more bickering about AV & disk encryption.
83 */
84
85
86/*********************************************************************************************************************************
87* Header Files *
88*********************************************************************************************************************************/
89#include <Windows.h>
90#include <Winternl.h>
91
92#include "../makeint.h"
93#include "../job.h"
94#include "../filedef.h"
95#include "../debug.h"
96#include "../kmkbuiltin.h"
97#include "winchildren.h"
98
99#include <assert.h>
100#include <process.h>
101#include <intrin.h>
102
103#include "nt/nt_child_inject_standard_handles.h"
104#include "console.h"
105
106#ifndef KMK_BUILTIN_STANDALONE
107extern void kmk_cache_exec_image_w(const wchar_t *); /* imagecache.c */
108#endif
109
110/* Option values from main.c: */
111extern const char *win_job_object_mode;
112extern const char *win_job_object_name;
113extern int win_job_object_no_kill;
114
115
116/*********************************************************************************************************************************
117* Defined Constants And Macros *
118*********************************************************************************************************************************/
119#define MKWINCHILD_MAX_PATH 1024
120
121#define MKWINCHILD_DO_SET_PROCESSOR_GROUP
122
123/** Checks the UTF-16 environment variable pointed to is the PATH. */
124#define IS_PATH_ENV_VAR(a_cwcVar, a_pwszVar) \
125 ( (a_cwcVar) >= 5 \
126 && (a_pwszVar)[4] == L'=' \
127 && ((a_pwszVar)[0] == L'P' || (a_pwszVar)[0] == L'p') \
128 && ((a_pwszVar)[1] == L'A' || (a_pwszVar)[1] == L'a') \
129 && ((a_pwszVar)[2] == L'T' || (a_pwszVar)[2] == L't') \
130 && ((a_pwszVar)[3] == L'H' || (a_pwszVar)[3] == L'h') )
131
132
133/*********************************************************************************************************************************
134* Structures and Typedefs *
135*********************************************************************************************************************************/
136/** Pointer to a childcare worker thread. */
137typedef struct WINCHILDCAREWORKER *PWINCHILDCAREWORKER;
138/** Pointer to a windows child process. */
139typedef struct WINCHILD *PWINCHILD;
140
141
142/**
143 * Child process type.
144 */
145typedef enum WINCHILDTYPE
146{
147 WINCHILDTYPE_INVALID = 0,
148 /** Normal child process. */
149 WINCHILDTYPE_PROCESS,
150#ifdef KMK
151 /** kmkbuiltin command. */
152 WINCHILDTYPE_BUILT_IN,
153 /** kmkbuiltin_append result write out. */
154 WINCHILDTYPE_APPEND,
155 /** kSubmit job. */
156 WINCHILDTYPE_SUBMIT,
157 /** kmk_redirect job. */
158 WINCHILDTYPE_REDIRECT,
159#endif
160 /** End of valid child types. */
161 WINCHILDTYPE_END
162} WINCHILDTYPE;
163
164
165/**
166 * Windows child process.
167 */
168typedef struct WINCHILD
169{
170 /** Magic / eyecatcher (WINCHILD_MAGIC). */
171 ULONG uMagic;
172 /** Child type. */
173 WINCHILDTYPE enmType;
174 /** Pointer to the next child process. */
175 PWINCHILD pNext;
176 /** The pid for this child. */
177 pid_t pid;
178 /** The make child structure associated with this child. */
179 struct child *pMkChild;
180
181 /** The process exit code. */
182 int iExitCode;
183 /** Kill signal, in case we or someone else killed it. */
184 int iSignal;
185 /** Set if core was dumped. */
186 int fCoreDumped;
187 /** Set if the a child process is a candidate for cl.exe where we supress
188 * annoying source name output. */
189 BOOL fProbableClExe;
190 /** The worker executing this child. */
191 PWINCHILDCAREWORKER pWorker;
192
193 /** Type specific data. */
194 union
195 {
196 /** Data for WINCHILDTYPE_PROCESS. */
197 struct
198 {
199 /** Argument vector (single allocation, strings following array). */
200 char **papszArgs;
201 /** Length of the argument strings. */
202 size_t cbArgsStrings;
203 /** Environment vector. Only a copy if fEnvIsCopy is set. */
204 char **papszEnv;
205 /** If we made a copy of the environment, this is the size of the
206 * strings and terminator string (not in array). This is done to
207 * speed up conversion, since MultiByteToWideChar can handle '\0'. */
208 size_t cbEnvStrings;
209 /** The make shell to use (copy). */
210 char *pszShell;
211 /** Handle to use for standard out. */
212 HANDLE hStdOut;
213 /** Handle to use for standard out. */
214 HANDLE hStdErr;
215 /** Whether to close hStdOut after creating the process. */
216 BOOL fCloseStdOut;
217 /** Whether to close hStdErr after creating the process. */
218 BOOL fCloseStdErr;
219 /** Whether to catch output from the process. */
220 BOOL fCatchOutput;
221
222 /** Child process handle. */
223 HANDLE hProcess;
224 } Process;
225
226 /** Data for WINCHILDTYPE_BUILT_IN. */
227 struct
228 {
229 /** The built-in command. */
230 PCKMKBUILTINENTRY pBuiltIn;
231 /** Number of arguments. */
232 int cArgs;
233 /** Argument vector (single allocation, strings following array). */
234 char **papszArgs;
235 /** Environment vector. Only a copy if fEnvIsCopy is set. */
236 char **papszEnv;
237 } BuiltIn;
238
239 /** Data for WINCHILDTYPE_APPEND. */
240 struct
241 {
242 /** The filename. */
243 char *pszFilename;
244 /** How much to append. */
245 size_t cbAppend;
246 /** What to append. */
247 char *pszAppend;
248 /** Whether to truncate the file. */
249 int fTruncate;
250 } Append;
251
252 /** Data for WINCHILDTYPE_SUBMIT. */
253 struct
254 {
255 /** The event we're to wait on (hooked up to a pipe) */
256 HANDLE hEvent;
257 /** Parameter for the cleanup callback. */
258 void *pvSubmitWorker;
259 /** Standard output catching pipe. Optional. */
260 PWINCCWPIPE pStdOut;
261 /** Standard error catching pipe. Optional. */
262 PWINCCWPIPE pStdErr;
263 } Submit;
264
265 /** Data for WINCHILDTYPE_REDIRECT. */
266 struct
267 {
268 /** Child process handle. */
269 HANDLE hProcess;
270 } Redirect;
271 } u;
272
273} WINCHILD;
274/** WINCHILD::uMagic value. */
275#define WINCHILD_MAGIC 0xbabebabeU
276
277
278/**
279 * Data for a windows childcare worker thread.
280 *
281 * We use one worker thread per child, reusing the threads when possible.
282 *
283 * This setup helps avoid the 64-bit handle with the WaitForMultipleObject API.
284 *
285 * It also helps using all CPUs on systems with more than one CPU group
286 * (typically systems with more than 64 CPU threads or/and multiple sockets, or
287 * special configs).
288 *
289 * This helps facilitates using pipes for collecting output child rather
290 * than temporary files. Pipes doesn't involve NTFS and can easily be reused.
291 *
292 * Finally, kBuild specific, this allows running kmkbuiltin_xxxx commands in
293 * threads.
294 */
295typedef struct WINCHILDCAREWORKER
296{
297 /** Magic / eyecatcher (WINCHILDCAREWORKER_MAGIC). */
298 ULONG uMagic;
299 /** The worker index. */
300 unsigned int idxWorker;
301 /** The processor group for this worker. */
302 unsigned int iProcessorGroup;
303 /** The thread ID. */
304 unsigned int tid;
305 /** The thread handle. */
306 HANDLE hThread;
307 /** The event the thread is idling on. */
308 HANDLE hEvtIdle;
309 /** The pipe catching standard output from a child. */
310 PWINCCWPIPE pStdOut;
311 /** The pipe catching standard error from a child. */
312 PWINCCWPIPE pStdErr;
313
314 /** Pointer to the current child. */
315 PWINCHILD volatile pCurChild;
316 /** List of children pending execution on this worker.
317 * This is updated atomitically just like g_pTailCompletedChildren. */
318 PWINCHILD volatile pTailTodoChildren;
319 /** TRUE if idle, FALSE if not. */
320 long volatile fIdle;
321} WINCHILDCAREWORKER;
322/** WINCHILD::uMagic value. */
323#define WINCHILDCAREWORKER_MAGIC 0xdad0dad0U
324
325
326/*********************************************************************************************************************************
327* Global Variables *
328*********************************************************************************************************************************/
329/** Whether it's initialized or not. */
330static BOOL g_fInitialized = FALSE;
331/** Set when we're shutting down everything. */
332static BOOL volatile g_fShutdown = FALSE;
333/** Event used to wait for children. */
334static HANDLE g_hEvtWaitChildren = INVALID_HANDLE_VALUE;
335/** Number of childcare workers currently in g_papChildCareworkers. */
336static unsigned g_cChildCareworkers = 0;
337/** Maximum number of childcare workers in g_papChildCareworkers. */
338static unsigned g_cChildCareworkersMax = 0;
339/** Pointer to childcare workers. */
340static PWINCHILDCAREWORKER *g_papChildCareworkers = NULL;
341/** The processor group allocator state. */
342static MKWINCHILDCPUGROUPALLOCSTATE g_ProcessorGroupAllocator;
343/** Number of processor groups in the system. */
344static unsigned g_cProcessorGroups = 1;
345/** Array detailing how many active processors there are in each group. */
346static unsigned const *g_pacProcessorsInGroup = &g_cProcessorGroups;
347/** Kernel32!GetActiveProcessorGroupCount */
348static WORD (WINAPI *g_pfnGetActiveProcessorGroupCount)(VOID);
349/** Kernel32!GetActiveProcessorCount */
350static DWORD (WINAPI *g_pfnGetActiveProcessorCount)(WORD);
351/** Kernel32!SetThreadGroupAffinity */
352static BOOL (WINAPI *g_pfnSetThreadGroupAffinity)(HANDLE, CONST GROUP_AFFINITY *, GROUP_AFFINITY *);
353/** NTDLL!NtQueryInformationProcess */
354static NTSTATUS (NTAPI *g_pfnNtQueryInformationProcess)(HANDLE, PROCESSINFOCLASS, PVOID, ULONG, PULONG);
355/** Set if the windows host is 64-bit. */
356static BOOL g_f64BitHost = (K_ARCH_BITS == 64);
357/** Windows version info.
358 * @note Putting this before the volatile stuff, hoping to keep it in a
359 * different cache line than the static bits above. */
360static OSVERSIONINFOA g_VersionInfo = { sizeof(g_VersionInfo), 4, 0, 1381, VER_PLATFORM_WIN32_NT, {0} };
361
362/** Children that has been completed.
363 * This is updated atomically, pushing completed children in LIFO fashion
364 * (thus 'tail'), then hitting g_hEvtWaitChildren if head. */
365static PWINCHILD volatile g_pTailCompletedChildren = NULL;
366
367/** Number of idle pending children.
368 * This is updated before g_hEvtWaitChildren is signalled. */
369static unsigned volatile g_cPendingChildren = 0;
370
371/** Number of idle childcare worker threads. */
372static unsigned volatile g_cIdleChildcareWorkers = 0;
373/** Index of the last idle child careworker (just a hint). */
374static unsigned volatile g_idxLastChildcareWorker = 0;
375
376#ifdef WITH_RW_LOCK
377/** RW lock for serializing kmkbuiltin_redirect and CreateProcess. */
378static SRWLOCK g_RWLock;
379#endif
380
381/** The job object for this make instance, if we created/opened one. */
382static HANDLE g_hJob = NULL;
383
384
385/*********************************************************************************************************************************
386* Internal Functions *
387*********************************************************************************************************************************/
388static void mkWinChildInitJobObjectAssociation(void);
389
390#if K_ARCH_BITS == 32 && !defined(_InterlockedCompareExchangePointer)
391/** _InterlockedCompareExchangePointer is missing? (VS2010) */
392K_INLINE void *_InterlockedCompareExchangePointer(void * volatile *ppvDst, void *pvNew, void *pvOld)
393{
394 return (void *)_InterlockedCompareExchange((long volatile *)ppvDst, (intptr_t)pvNew, (intptr_t)pvOld);
395}
396#endif
397
398
399/**
400 * Initializes the windows child module.
401 *
402 * @param cJobSlots The number of job slots.
403 */
404void MkWinChildInit(unsigned int cJobSlots)
405{
406 HMODULE hmod;
407
408 /*
409 * Figure out how many childcare workers first.
410 */
411 static unsigned int const s_cMaxWorkers = 4096;
412 unsigned cWorkers;
413 if (cJobSlots >= 1 && cJobSlots < s_cMaxWorkers)
414 cWorkers = cJobSlots;
415 else
416 cWorkers = s_cMaxWorkers;
417
418 /*
419 * Allocate the array and the child completed event object.
420 */
421 g_papChildCareworkers = (PWINCHILDCAREWORKER *)xcalloc(cWorkers * sizeof(g_papChildCareworkers[0]));
422 g_cChildCareworkersMax = cWorkers;
423
424 g_hEvtWaitChildren = CreateEvent(NULL, FALSE /*fManualReset*/, FALSE /*fInitialState*/, NULL /*pszName*/);
425 if (!g_hEvtWaitChildren)
426 fatal(NILF, INTSTR_LENGTH, _("MkWinChildInit: CreateEvent failed: %u"), GetLastError());
427
428 /*
429 * NTDLL imports that we need.
430 */
431 hmod = GetModuleHandleA("NTDLL.DLL");
432 *(FARPROC *)&g_pfnNtQueryInformationProcess = GetProcAddress(hmod, "NtQueryInformationProcess");
433 if (!g_pfnNtQueryInformationProcess)
434 fatal(NILF, 0, _("MkWinChildInit: NtQueryInformationProcess not found"));
435
436#if K_ARCH_BITS == 32
437 /*
438 * Initialize g_f64BitHost.
439 */
440 if (!IsWow64Process(GetCurrentProcess(), &g_f64BitHost))
441 fatal(NILF, INTSTR_LENGTH, _("MkWinChildInit: IsWow64Process failed: %u"), GetLastError());
442#elif K_ARCH_BITS == 64
443 assert(g_f64BitHost);
444#else
445# error "K_ARCH_BITS is bad/missing"
446#endif
447
448 /*
449 * Figure out how many processor groups there are.
450 * For that we need to first figure the windows version.
451 */
452 if (!GetVersionExA(&g_VersionInfo))
453 {
454 DWORD uRawVer = GetVersion();
455 g_VersionInfo.dwMajorVersion = uRawVer & 0xff;
456 g_VersionInfo.dwMinorVersion = (uRawVer >> 8) & 0xff;
457 g_VersionInfo.dwBuildNumber = (uRawVer >> 16) & 0x7fff;
458 }
459 if (g_VersionInfo.dwMajorVersion >= 6)
460 {
461 hmod = GetModuleHandleA("KERNEL32.DLL");
462 *(FARPROC *)&g_pfnGetActiveProcessorGroupCount = GetProcAddress(hmod, "GetActiveProcessorGroupCount");
463 *(FARPROC *)&g_pfnGetActiveProcessorCount = GetProcAddress(hmod, "GetActiveProcessorCount");
464 *(FARPROC *)&g_pfnSetThreadGroupAffinity = GetProcAddress(hmod, "SetThreadGroupAffinity");
465 if ( g_pfnSetThreadGroupAffinity
466 && g_pfnGetActiveProcessorCount
467 && g_pfnGetActiveProcessorGroupCount)
468 {
469 unsigned int *pacProcessorsInGroup;
470 unsigned iGroup;
471 g_cProcessorGroups = g_pfnGetActiveProcessorGroupCount();
472 if (g_cProcessorGroups == 0)
473 g_cProcessorGroups = 1;
474
475 pacProcessorsInGroup = (unsigned int *)xmalloc(sizeof(g_pacProcessorsInGroup[0]) * g_cProcessorGroups);
476 g_pacProcessorsInGroup = pacProcessorsInGroup;
477 for (iGroup = 0; iGroup < g_cProcessorGroups; iGroup++)
478 pacProcessorsInGroup[iGroup] = g_pfnGetActiveProcessorCount(iGroup);
479
480 MkWinChildInitCpuGroupAllocator(&g_ProcessorGroupAllocator);
481 }
482 else
483 {
484 g_pfnSetThreadGroupAffinity = NULL;
485 g_pfnGetActiveProcessorCount = NULL;
486 g_pfnGetActiveProcessorGroupCount = NULL;
487 }
488 }
489
490#ifdef WITH_RW_LOCK
491 /*
492 * For serializing with standard file handle manipulation (kmkbuiltin_redirect).
493 */
494 InitializeSRWLock(&g_RWLock);
495#endif
496
497 /*
498 * Associate with a job object.
499 */
500 mkWinChildInitJobObjectAssociation();
501
502 /*
503 * This is dead code that was thought to fix a problem observed doing
504 * `tcc.exe /c "kmk |& tee bld.log"` and leading to a crash in cl.exe
505 * when spawned with fInheritHandles = FALSE, see hStdErr=NULL in the
506 * child. However, it turns out this was probably caused by not clearing
507 * the CRT file descriptor and handle table in the startup info.
508 * Leaving the code here in case it comes in handy after all.
509 */
510#if 0
511 {
512 struct
513 {
514 DWORD uStdHandle;
515 HANDLE hHandle;
516 } aHandles[3] = { { STD_INPUT_HANDLE, NULL }, { STD_OUTPUT_HANDLE, NULL }, { STD_ERROR_HANDLE, NULL } };
517 int i;
518
519 for (i = 0; i < 3; i++)
520 aHandles[i].hHandle = GetStdHandle(aHandles[i].uStdHandle);
521
522 for (i = 0; i < 3; i++)
523 if ( aHandles[i].hHandle == NULL
524 || aHandles[i].hHandle == INVALID_HANDLE_VALUE)
525 {
526 int fd = open("nul", _O_RDWR);
527 if (fd >= 0)
528 {
529 if (_dup2(fd, i) >= 0)
530 {
531 assert((HANDLE)_get_osfhandle(i) != aHandles[i].hHandle);
532 assert((HANDLE)_get_osfhandle(i) == GetStdHandle(aHandles[i].uStdHandle));
533 }
534 else
535 ONNNS(fatal, NILF, "_dup2(%d('nul'), %d) failed: %u (%s)", fd, i, errno, strerror(errno));
536 if (fd != i)
537 close(fd);
538 }
539 else
540 ONNS(fatal, NILF, "open(nul,RW) failed: %u (%s)", i, errno, strerror(errno));
541 }
542 else
543 {
544 int j;
545 for (j = i + 1; j < 3; j++)
546 if (aHandles[j].hHandle == aHandles[i].hHandle)
547 {
548 int fd = _dup(j);
549 if (fd >= 0)
550 {
551 if (_dup2(fd, j) >= 0)
552 {
553 aHandles[j].hHandle = (HANDLE)_get_osfhandle(j);
554 assert(aHandles[j].hHandle != aHandles[i].hHandle);
555 assert(aHandles[j].hHandle == GetStdHandle(aHandles[j].uStdHandle));
556 }
557 else
558 ONNNS(fatal, NILF, "_dup2(%d, %d) failed: %u (%s)", fd, j, errno, strerror(errno));
559 if (fd != j)
560 close(fd);
561 }
562 else
563 ONNS(fatal, NILF, "_dup(%d) failed: %u (%s)", j, errno, strerror(errno));
564 }
565 }
566 }
567#endif
568}
569
570/**
571 * Create or open a job object for this make instance and its children.
572 *
573 * Depending on the --job-object=mode value, we typically create/open a job
574 * object here if we're the root make instance. The job object is then
575 * typically configured to kill all remaining processes when the root make
576 * terminates, so that there aren't any stuck processes around messing up
577 * subsequent builds. This is very handy on build servers.
578 *
579 * If we're it no-kill mode, the job object is pretty pointless for manual
580 * cleanup as the job object becomes invisible (or something) when the last
581 * handle to it closes, i.e. g_hJob. On windows 8 and later it looks
582 * like any orphaned children are immediately assigned to the parent job
583 * object. Too bad for kmk_kill and such.
584 *
585 * win_job_object_mode values: login, root, each, none
586 */
587static void mkWinChildInitJobObjectAssociation(void)
588{
589 BOOL fCreate = TRUE;
590 char szJobName[128];
591 const char *pszJobName = win_job_object_name;
592
593 /* Skip if disabled. */
594 if (strcmp(win_job_object_mode, "none") == 0)
595 return;
596
597 /* Skip if not root make instance, unless we're having one job object
598 per make instance. */
599 if ( makelevel != 0
600 && strcmp(win_job_object_mode, "each") != 0)
601 return;
602
603 /* Format the the default job object name if --job-object-name
604 wasn't given. */
605 if (!pszJobName || *pszJobName == '\0')
606 {
607 pszJobName = szJobName;
608 if (strcmp(win_job_object_mode, "login") == 0)
609 {
610 /* Use the AuthenticationId like mspdbsrv.exe does. */
611 HANDLE hToken;
612 if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken))
613 {
614 TOKEN_STATISTICS TokenStats;
615 DWORD cbRet = 0;
616 memset(&TokenStats, 0, sizeof(TokenStats));
617 if (GetTokenInformation(hToken, TokenStatistics, &TokenStats, sizeof(TokenStats), &cbRet))
618 snprintf(szJobName, sizeof(szJobName), "kmk-job-obj-login-%08x.%08x",
619 (unsigned)TokenStats.AuthenticationId.HighPart, (unsigned)TokenStats.AuthenticationId.LowPart);
620 else
621 {
622 ONN(message, 0, _("GetTokenInformation failed: %u (cbRet=%u)"), GetLastError(), cbRet);
623 return;
624 }
625 CloseHandle(hToken);
626 }
627 else
628 {
629 ON(message, 0, _("OpenProcessToken failed: %u"), GetLastError());
630 return;
631 }
632 }
633 else
634 {
635 SYSTEMTIME Now = {0};
636 GetSystemTime(&Now);
637 snprintf(szJobName, sizeof(szJobName), "kmk-job-obj-%04u-%02u-%02uT%02u-%02u-%02uZ%u",
638 Now.wYear, Now.wMonth, Now.wDay, Now.wHour, Now.wMinute, Now.wSecond, getpid());
639 }
640 }
641
642 /* In login mode and when given a job object name, we try open it first. */
643 if ( win_job_object_name
644 || strcmp(win_job_object_mode, "login") == 0)
645 {
646 g_hJob = OpenJobObjectA(JOB_OBJECT_ASSIGN_PROCESS, win_job_object_no_kill /*bInheritHandle*/, pszJobName);
647 if (g_hJob)
648 fCreate = FALSE;
649 else
650 {
651 DWORD dwErr = GetLastError();
652 if (dwErr != ERROR_PATH_NOT_FOUND && dwErr != ERROR_FILE_NOT_FOUND)
653 {
654 OSN(message, 0, _("OpenJobObjectA(,,%s) failed: %u"), pszJobName, GetLastError());
655 return;
656 }
657 }
658 }
659
660 if (fCreate)
661 {
662 SECURITY_ATTRIBUTES SecAttr = { sizeof(SecAttr), NULL, TRUE /*bInheritHandle*/ };
663 g_hJob = CreateJobObjectA(win_job_object_no_kill ? &SecAttr : NULL, pszJobName);
664 if (g_hJob)
665 {
666 /* We need to set the BREAKAWAY_OK flag, as we don't want make CreateProcess
667 fail if someone tries to break way. Also set KILL_ON_JOB_CLOSE unless
668 --job-object-no-kill is given. */
669 JOBOBJECT_EXTENDED_LIMIT_INFORMATION Info;
670 DWORD cbActual = 0;
671 memset(&Info, 0, sizeof(Info));
672 if (QueryInformationJobObject(g_hJob, JobObjectExtendedLimitInformation, &Info, sizeof(Info), &cbActual))
673 {
674 Info.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_BREAKAWAY_OK;
675 if (!win_job_object_no_kill)
676 Info.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
677 else
678 Info.BasicLimitInformation.LimitFlags &= ~JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
679 if (!SetInformationJobObject(g_hJob, JobObjectExtendedLimitInformation, &Info, sizeof(Info)))
680 OSSN(message, 0, _("SetInformationJobObject(%s,JobObjectExtendedLimitInformation,{%s},) failed: %u"),
681 pszJobName, win_job_object_mode, GetLastError());
682 }
683 else
684 OSN(message, 0, _("QueryInformationJobObject(%s,JobObjectExtendedLimitInformation,,,) failed: %u"),
685 pszJobName, GetLastError());
686 }
687 else
688 {
689 OSN(message, 0, _("CreateJobObjectA(NULL,%s) failed: %u"), pszJobName, GetLastError());
690 return;
691 }
692 }
693
694 /* Make it our job object. */
695 if (!(AssignProcessToJobObject(g_hJob, GetCurrentProcess())))
696 OSN(message, 0, _("AssignProcessToJobObject(%s, me) failed: %u"), pszJobName, GetLastError());
697}
698
699/**
700 * Used by mkWinChildcareWorkerThread() and MkWinChildWait() to get the head
701 * child from a lifo (g_pTailCompletedChildren, pTailTodoChildren).
702 *
703 * @returns Head child.
704 * @param ppTail Pointer to the child variable.
705 * @param pChild Tail child.
706 */
707static PWINCHILD mkWinChildDequeFromLifo(PWINCHILD volatile *ppTail, PWINCHILD pChild)
708{
709 if (pChild->pNext)
710 {
711 PWINCHILD pPrev;
712 do
713 {
714 pPrev = pChild;
715 pChild = pChild->pNext;
716 } while (pChild->pNext);
717 pPrev->pNext = NULL;
718 }
719 else
720 {
721 PWINCHILD const pWantedChild = pChild;
722 pChild = _InterlockedCompareExchangePointer(ppTail, NULL, pWantedChild);
723 if (pChild != pWantedChild)
724 {
725 PWINCHILD pPrev;
726 do
727 {
728 pPrev = pChild;
729 pChild = pChild->pNext;
730 } while (pChild->pNext);
731 pPrev->pNext = NULL;
732 assert(pChild == pWantedChild);
733 }
734 }
735 return pChild;
736}
737
738/**
739 * Output error message while running on a worker thread.
740 *
741 * @returns -1
742 * @param pWorker The calling worker. Mainly for getting the
743 * current child and its stderr output unit. Pass
744 * NULL if the output should go thru the child
745 * stderr buffering.
746 * @param iType The error type:
747 * - 0: more of a info directly to stdout,
748 * - 1: child related error,
749 * - 2: child related error for immedate release.
750 * @param pszFormat The message format string.
751 * @param ... Argument for the message.
752 */
753static int MkWinChildError(PWINCHILDCAREWORKER pWorker, int iType, const char *pszFormat, ...)
754{
755 /*
756 * Format the message into stack buffer.
757 */
758 char szMsg[4096];
759 int cchMsg;
760 int cchPrefix;
761 va_list va;
762
763 /* Compose the prefix, being paranoid about it not exceeding the buffer in any way. */
764 const char *pszInfix = iType == 0 ? "info: " : "error: ";
765 const char *pszProgram = program;
766 if (strlen(pszProgram) > 80)
767 {
768#ifdef KMK
769 pszProgram = "kmk";
770#else
771 pszProgram = "gnumake";
772#endif
773 }
774 if (makelevel == 0)
775 cchPrefix = snprintf(szMsg, sizeof(szMsg) / 2, "%s: %s", pszProgram, pszInfix);
776 else
777 cchPrefix = snprintf(szMsg, sizeof(szMsg) / 2, "%s[%u]: %s", pszProgram, makelevel, pszInfix);
778 assert(cchPrefix < sizeof(szMsg) / 2 && cchPrefix > 0);
779
780 /* Format the user specified message. */
781 va_start(va, pszFormat);
782 cchMsg = vsnprintf(&szMsg[cchPrefix], sizeof(szMsg) - 2 - cchPrefix, pszFormat, va);
783 va_end(va);
784 szMsg[sizeof(szMsg) - 2] = '\0';
785 cchMsg = strlen(szMsg);
786
787 /* Make sure there's a newline at the end of it (we reserved space for that). */
788 if (cchMsg <= 0 || szMsg[cchMsg - 1] != '\n')
789 {
790 szMsg[cchMsg++] = '\n';
791 szMsg[cchMsg] = '\0';
792 }
793
794#ifdef CONFIG_WITH_OUTPUT_IN_MEMORY
795 /*
796 * Try use the stderr of the current child of the worker.
797 */
798 if ( iType != 0
799 && iType != 3
800 && pWorker)
801 {
802 PWINCHILD pChild = pWorker->pCurChild;
803 if (pChild)
804 {
805 struct child *pMkChild = pChild->pMkChild;
806 if (pMkChild)
807 {
808 output_write_text(&pMkChild->output, 1 /*is_err*/, szMsg, cchMsg);
809 return -1;
810 }
811 }
812 }
813#endif
814
815 /*
816 * Fallback to writing directly to stderr.
817 */
818 maybe_con_fwrite(szMsg, cchMsg, 1, iType == 0 ? stdout : stderr);
819 return -1;
820}
821
822/**
823 * Duplicates the given UTF-16 string.
824 *
825 * @returns 0
826 * @param pwszSrc The UTF-16 string to duplicate.
827 * @param cwcSrc Length, may include the terminator.
828 * @param ppwszDst Where to return the duplicate.
829 */
830static int mkWinChildDuplicateUtf16String(const WCHAR *pwszSrc, size_t cwcSrc, WCHAR **ppwszDst)
831{
832 size_t cb = sizeof(WCHAR) * cwcSrc;
833 if (cwcSrc > 0 && pwszSrc[cwcSrc - 1] == L'\0')
834 *ppwszDst = (WCHAR *)memcpy(xmalloc(cb), pwszSrc, cb);
835 else
836 {
837 WCHAR *pwszDst = (WCHAR *)xmalloc(cb + sizeof(WCHAR));
838 memcpy(pwszDst, pwszSrc, cb);
839 pwszDst[cwcSrc] = L'\0';
840 *ppwszDst = pwszDst;
841 }
842 return 0;
843}
844
845
846/**
847 * Used to flush data we're read but not yet written at the termination of a
848 * process.
849 *
850 * @param pChild The child.
851 * @param pPipe The pipe.
852 */
853static void mkWinChildcareWorkerFlushUnwritten(PWINCHILD pChild, PWINCCWPIPE pPipe)
854{
855 DWORD cbUnwritten = pPipe->offPendingRead - pPipe->cbWritten;
856 assert(pPipe->cbWritten <= pPipe->cbBuffer - 16);
857 assert(pPipe->offPendingRead <= pPipe->cbBuffer - 16);
858 if (cbUnwritten)
859 {
860#ifdef CONFIG_WITH_OUTPUT_IN_MEMORY
861 if (pChild && pChild->pMkChild)
862 {
863 output_write_bin(&pChild->pMkChild->output, pPipe->iWhich == 2, &pPipe->pbBuffer[pPipe->cbWritten], cbUnwritten);
864 pPipe->cbWritten += cbUnwritten;
865 }
866 else
867#endif
868 {
869 DWORD cbWritten = 0;
870 if (WriteFile(GetStdHandle(pPipe->iWhich == 1 ? STD_OUTPUT_HANDLE : STD_ERROR_HANDLE),
871 &pPipe->pbBuffer[pPipe->cbWritten], cbUnwritten, &cbWritten, NULL))
872 pPipe->cbWritten += cbWritten <= cbUnwritten ? cbWritten : cbUnwritten; /* paranoia */
873 }
874 pPipe->fHaveWrittenOut = TRUE;
875 }
876}
877
878/**
879 * This logic mirrors kwSandboxConsoleFlushAll.
880 *
881 * @returns TRUE if it looks like a CL.EXE source line, otherwise FALSE.
882 * @param pPipe The pipe.
883 * @param offStart The start of the output in the pipe buffer.
884 * @param offEnd The end of the output in the pipe buffer.
885 */
886static BOOL mkWinChildcareWorkerIsClExeSourceLine(PWINCCWPIPE pPipe, DWORD offStart, DWORD offEnd)
887{
888 if (offEnd < offStart + 2)
889 return FALSE;
890 if (offEnd - offStart > 80)
891 return FALSE;
892
893 if ( pPipe->pbBuffer[offEnd - 2] != '\r'
894 || pPipe->pbBuffer[offEnd - 1] != '\n')
895 return FALSE;
896
897 offEnd -= 2;
898 while (offEnd-- > offStart)
899 {
900 char ch = pPipe->pbBuffer[offEnd];
901 if (isalnum(ch) || ch == '.' || ch == ' ' || ch == '_' || ch == '-')
902 { /* likely */ }
903 else
904 return FALSE;
905 }
906
907 return TRUE;
908}
909
910/**
911 * Adds output to the given standard output for the child.
912 *
913 * There is no pending read when this function is called, so we're free to
914 * reshuffle the buffer if desirable.
915 *
916 * @param pChild The child. Optional (kSubmit).
917 * @param iWhich Which standard descriptor number.
918 * @param cbNewData How much more output was caught.
919 */
920static void mkWinChildcareWorkerCaughtMoreOutput(PWINCHILD pChild, PWINCCWPIPE pPipe, DWORD cbNewData)
921{
922 DWORD offStart = pPipe->cbWritten;
923 assert(offStart <= pPipe->offPendingRead);
924 assert(offStart <= pPipe->cbBuffer - 16);
925 assert(pPipe->offPendingRead <= pPipe->cbBuffer - 16);
926 if (cbNewData > 0)
927 {
928 DWORD offRest;
929
930 /* Move offPendingRead ahead by cbRead. */
931 pPipe->offPendingRead += cbNewData;
932 assert(pPipe->offPendingRead <= pPipe->cbBuffer);
933 if (pPipe->offPendingRead > pPipe->cbBuffer)
934 pPipe->offPendingRead = pPipe->cbBuffer;
935
936 /* Locate the last newline in the buffer. */
937 offRest = pPipe->offPendingRead;
938 while (offRest > offStart && pPipe->pbBuffer[offRest - 1] != '\n')
939 offRest--;
940
941 /* If none were found and we've less than 16 bytes left in the buffer, try
942 find a word boundrary to flush on instead. */
943 if ( offRest <= offStart
944 && pPipe->cbBuffer - pPipe->offPendingRead + offStart < 16)
945 {
946 offRest = pPipe->offPendingRead;
947 while ( offRest > offStart
948 && isalnum(pPipe->pbBuffer[offRest - 1]))
949 offRest--;
950 if (offRest == offStart)
951 offRest = pPipe->offPendingRead;
952 }
953 /* If this is a potential CL.EXE process, we will keep the source
954 filename unflushed and maybe discard it at the end. */
955 else if ( pChild
956 && pChild->fProbableClExe
957 && pPipe->iWhich == 1
958 && offRest == pPipe->offPendingRead
959 && mkWinChildcareWorkerIsClExeSourceLine(pPipe, offStart, offRest))
960 offRest = offStart;
961
962 if (offRest > offStart)
963 {
964 /* Write out offStart..offRest. */
965 DWORD cbToWrite = offRest - offStart;
966#ifdef CONFIG_WITH_OUTPUT_IN_MEMORY
967 if (pChild && pChild->pMkChild)
968 {
969 output_write_bin(&pChild->pMkChild->output, pPipe->iWhich == 2, &pPipe->pbBuffer[offStart], cbToWrite);
970 offStart += cbToWrite;
971 pPipe->cbWritten = offStart;
972 }
973 else
974#endif
975 {
976 DWORD cbWritten = 0;
977 if (WriteFile(GetStdHandle(pPipe->iWhich == 1 ? STD_OUTPUT_HANDLE : STD_ERROR_HANDLE),
978 &pPipe->pbBuffer[offStart], cbToWrite, &cbWritten, NULL))
979 {
980 offStart += cbWritten <= cbToWrite ? cbWritten : cbToWrite; /* paranoia */
981 pPipe->cbWritten = offStart;
982 }
983 }
984 pPipe->fHaveWrittenOut = TRUE;
985 }
986 }
987
988 /* Shuffle the data to the front of the buffer. */
989 if (offStart > 0)
990 {
991 DWORD cbUnwritten = pPipe->offPendingRead - offStart;
992 if (cbUnwritten > 0)
993 memmove(pPipe->pbBuffer, &pPipe->pbBuffer[offStart], cbUnwritten);
994 pPipe->offPendingRead -= pPipe->cbWritten;
995 pPipe->cbWritten = 0;
996 }
997}
998
999/**
1000 * Catches output from the given pipe.
1001 *
1002 * @param pChild The child. Optional (kSubmit).
1003 * @param pPipe The pipe.
1004 * @param fDraining Set if we're draining the pipe after the process
1005 * terminated.
1006 */
1007static void mkWinChildcareWorkerCatchOutput(PWINCHILD pChild, PWINCCWPIPE pPipe, BOOL fDraining)
1008{
1009 /*
1010 * Deal with already pending read.
1011 */
1012 if (pPipe->fReadPending)
1013 {
1014 DWORD cbRead = 0;
1015 if (GetOverlappedResult(pPipe->hPipeMine, &pPipe->Overlapped, &cbRead, !fDraining))
1016 {
1017 mkWinChildcareWorkerCaughtMoreOutput(pChild, pPipe, cbRead);
1018 pPipe->fReadPending = FALSE;
1019 }
1020 else if (fDraining && GetLastError() == ERROR_IO_INCOMPLETE)
1021 return;
1022 else
1023 {
1024 MkWinChildError(pChild ? pChild->pWorker : NULL, 2, "GetOverlappedResult failed: %u\n", GetLastError());
1025 pPipe->fReadPending = FALSE;
1026 if (fDraining)
1027 return;
1028 }
1029 }
1030
1031 /*
1032 * Read data till one becomes pending.
1033 */
1034 for (;;)
1035 {
1036 DWORD cbRead;
1037
1038 memset(&pPipe->Overlapped, 0, sizeof(pPipe->Overlapped));
1039 pPipe->Overlapped.hEvent = pPipe->hEvent;
1040 ResetEvent(pPipe->hEvent);
1041
1042 assert(pPipe->offPendingRead < pPipe->cbBuffer);
1043 SetLastError(0);
1044 cbRead = 0;
1045 if (!ReadFile(pPipe->hPipeMine, &pPipe->pbBuffer[pPipe->offPendingRead],
1046 pPipe->cbBuffer - pPipe->offPendingRead, &cbRead, &pPipe->Overlapped))
1047 {
1048 DWORD dwErr = GetLastError();
1049 if (dwErr == ERROR_IO_PENDING)
1050 pPipe->fReadPending = TRUE;
1051 else
1052 MkWinChildError(pChild ? pChild->pWorker : NULL, 2,
1053 "ReadFile failed on standard %s: %u\n",
1054 pPipe->iWhich == 1 ? "output" : "error", GetLastError());
1055 return;
1056 }
1057
1058 mkWinChildcareWorkerCaughtMoreOutput(pChild, pPipe, cbRead);
1059 }
1060}
1061
1062/**
1063 * Makes sure the output pipes are drained and pushed to output.
1064 *
1065 * @param pChild The child. Optional (kSubmit).
1066 * @param pStdOut The standard output pipe structure.
1067 * @param pStdErr The standard error pipe structure.
1068 */
1069void MkWinChildcareWorkerDrainPipes(PWINCHILD pChild, PWINCCWPIPE pStdOut, PWINCCWPIPE pStdErr)
1070{
1071 mkWinChildcareWorkerCatchOutput(pChild, pStdOut, TRUE /*fDraining*/);
1072 mkWinChildcareWorkerCatchOutput(pChild, pStdErr, TRUE /*fDraining*/);
1073
1074 /* Drop lone 'source.c' line from CL.exe, but only if no other output at all. */
1075 if ( pChild
1076 && pChild->fProbableClExe
1077 && !pStdOut->fHaveWrittenOut
1078 && !pStdErr->fHaveWrittenOut
1079 && pStdErr->cbWritten == pStdErr->offPendingRead
1080 && pStdOut->cbWritten < pStdOut->offPendingRead
1081 && mkWinChildcareWorkerIsClExeSourceLine(pStdOut, pStdOut->cbWritten, pStdOut->offPendingRead))
1082 {
1083 if (!pStdOut->fReadPending)
1084 pStdOut->cbWritten = pStdOut->offPendingRead = 0;
1085 else
1086 pStdOut->cbWritten = pStdOut->offPendingRead;
1087 }
1088 else
1089 {
1090 mkWinChildcareWorkerFlushUnwritten(pChild, pStdOut);
1091 mkWinChildcareWorkerFlushUnwritten(pChild, pStdErr);
1092 }
1093}
1094
1095/**
1096 * Commmon worker for waiting on a child process and retrieving the exit code.
1097 *
1098 * @returns Child exit code.
1099 * @param pWorker The worker.
1100 * @param pChild The child.
1101 * @param hProcess The process handle.
1102 * @param pwszJob The job name.
1103 * @param fCatchOutput Set if we need to work the output pipes
1104 * associated with the worker.
1105 */
1106static int mkWinChildcareWorkerWaitForProcess(PWINCHILDCAREWORKER pWorker, PWINCHILD pChild, HANDLE hProcess,
1107 WCHAR const *pwszJob, BOOL fCatchOutput)
1108{
1109 DWORD const msStart = GetTickCount();
1110 DWORD msNextMsg = msStart + 15000;
1111
1112 /* Reset the written indicators on the pipes before we start loop. */
1113 pWorker->pStdOut->fHaveWrittenOut = FALSE;
1114 pWorker->pStdErr->fHaveWrittenOut = FALSE;
1115
1116 for (;;)
1117 {
1118 /*
1119 * Do the waiting and output catching.
1120 */
1121 DWORD dwStatus;
1122 if (!fCatchOutput)
1123 dwStatus = WaitForSingleObject(hProcess, 15001 /*ms*/);
1124 else
1125 {
1126 HANDLE ahHandles[3] = { hProcess, pWorker->pStdOut->hEvent, pWorker->pStdErr->hEvent };
1127 dwStatus = WaitForMultipleObjects(3, ahHandles, FALSE /*fWaitAll*/, 1000 /*ms*/);
1128 if (dwStatus == WAIT_OBJECT_0 + 1)
1129 mkWinChildcareWorkerCatchOutput(pChild, pWorker->pStdOut, FALSE /*fDraining*/);
1130 else if (dwStatus == WAIT_OBJECT_0 + 2)
1131 mkWinChildcareWorkerCatchOutput(pChild, pWorker->pStdErr, FALSE /*fDraining*/);
1132 }
1133 assert(dwStatus != WAIT_FAILED);
1134
1135 /*
1136 * Get the exit code and return if the process was signalled as done.
1137 */
1138 if (dwStatus == WAIT_OBJECT_0)
1139 {
1140 DWORD dwExitCode = -42;
1141 if (GetExitCodeProcess(hProcess, &dwExitCode))
1142 {
1143 pChild->iExitCode = (int)dwExitCode;
1144 if (fCatchOutput)
1145 MkWinChildcareWorkerDrainPipes(pChild, pWorker->pStdOut, pWorker->pStdErr);
1146 return dwExitCode;
1147 }
1148 }
1149 /*
1150 * Loop again if just a timeout or pending output?
1151 * Put out a message every 15 or 30 seconds if the job takes a while.
1152 */
1153 else if ( dwStatus == WAIT_TIMEOUT
1154 || dwStatus == WAIT_OBJECT_0 + 1
1155 || dwStatus == WAIT_OBJECT_0 + 2
1156 || dwStatus == WAIT_IO_COMPLETION)
1157 {
1158 DWORD msNow = GetTickCount();
1159 if (msNow >= msNextMsg)
1160 {
1161 if ( !pChild->pMkChild
1162 || !pChild->pMkChild->recursive) /* ignore make recursions */
1163 {
1164 if ( !pChild->pMkChild
1165 || !pChild->pMkChild->file
1166 || !pChild->pMkChild->file->name)
1167 MkWinChildError(NULL, 0, "Pid %u ('%ls') still running after %u seconds\n",
1168 GetProcessId(hProcess), pwszJob, (msNow - msStart) / 1000);
1169 else
1170 MkWinChildError(NULL, 0, "Target '%s' (pid %u) still running after %u seconds\n",
1171 pChild->pMkChild->file->name, GetProcessId(hProcess), (msNow - msStart) / 1000);
1172 }
1173
1174 /* After 15s, 30s, 60s, 120s, 180s, ... */
1175 if (msNextMsg == msStart + 15000)
1176 msNextMsg += 15000;
1177 else
1178 msNextMsg += 30000;
1179 }
1180 continue;
1181 }
1182
1183 /* Something failed. */
1184 pChild->iExitCode = GetLastError();
1185 if (pChild->iExitCode == 0)
1186 pChild->iExitCode = -4242;
1187 return pChild->iExitCode;
1188 }
1189}
1190
1191
1192/**
1193 * Closes standard handles that need closing before destruction.
1194 *
1195 * @param pChild The child (WINCHILDTYPE_PROCESS).
1196 */
1197static void mkWinChildcareWorkerCloseStandardHandles(PWINCHILD pChild)
1198{
1199 if ( pChild->u.Process.fCloseStdOut
1200 && pChild->u.Process.hStdOut != INVALID_HANDLE_VALUE)
1201 {
1202 CloseHandle(pChild->u.Process.hStdOut);
1203 pChild->u.Process.hStdOut = INVALID_HANDLE_VALUE;
1204 pChild->u.Process.fCloseStdOut = FALSE;
1205 }
1206 if ( pChild->u.Process.fCloseStdErr
1207 && pChild->u.Process.hStdErr != INVALID_HANDLE_VALUE)
1208 {
1209 CloseHandle(pChild->u.Process.hStdErr);
1210 pChild->u.Process.hStdErr = INVALID_HANDLE_VALUE;
1211 pChild->u.Process.fCloseStdErr = FALSE;
1212 }
1213}
1214
1215
1216/**
1217 * Does the actual process creation.
1218 *
1219 * @returns 0 if there is anything to wait on, otherwise non-zero windows error.
1220 * @param pWorker The childcare worker.
1221 * @param pChild The child.
1222 * @param pwszImageName The image path.
1223 * @param pwszCommandLine The command line.
1224 * @param pwszzEnvironment The enviornment block.
1225 */
1226static int mkWinChildcareWorkerCreateProcess(PWINCHILDCAREWORKER pWorker, WCHAR const *pwszImageName,
1227 WCHAR const *pwszCommandLine, WCHAR const *pwszzEnvironment, WCHAR const *pwszCwd,
1228 BOOL pafReplace[3], HANDLE pahChild[3], BOOL fCatchOutput, HANDLE *phProcess)
1229{
1230 PROCESS_INFORMATION ProcInfo;
1231 STARTUPINFOW StartupInfo;
1232 DWORD fFlags = CREATE_UNICODE_ENVIRONMENT;
1233 BOOL const fHaveHandles = pafReplace[0] | pafReplace[1] | pafReplace[2];
1234 BOOL fRet;
1235 DWORD dwErr;
1236#ifdef KMK
1237 extern int process_priority;
1238#endif
1239
1240 /*
1241 * Populate startup info.
1242 *
1243 * Turns out we can get away without passing TRUE for the inherit handles
1244 * parameter to CreateProcess when we're not using STARTF_USESTDHANDLES.
1245 * At least on NT, which is all worth caring about at this point + context IMO.
1246 *
1247 * Not inherting the handles is a good thing because it means we won't
1248 * accidentally end up with a pipe handle or such intended for a different
1249 * child process, potentially causing the EOF/HUP event to be delayed.
1250 *
1251 * Since the present handle inhertiance requirements only involves standard
1252 * output and error, we'll never set the inherit handles flag and instead
1253 * do manual handle duplication and planting.
1254 */
1255 memset(&StartupInfo, 0, sizeof(StartupInfo));
1256 StartupInfo.cb = sizeof(StartupInfo);
1257 GetStartupInfoW(&StartupInfo);
1258 StartupInfo.lpReserved2 = 0; /* No CRT file handle + descriptor info possible, sorry. */
1259 StartupInfo.cbReserved2 = 0;
1260 if ( !fHaveHandles
1261 && !fCatchOutput)
1262 StartupInfo.dwFlags &= ~STARTF_USESTDHANDLES;
1263 else
1264 {
1265 fFlags |= CREATE_SUSPENDED;
1266 StartupInfo.dwFlags &= ~STARTF_USESTDHANDLES;
1267 }
1268
1269 /*
1270 * Flags.
1271 */
1272#ifdef KMK
1273 switch (process_priority)
1274 {
1275 case 1: fFlags |= CREATE_SUSPENDED | IDLE_PRIORITY_CLASS; break;
1276 case 2: fFlags |= CREATE_SUSPENDED | BELOW_NORMAL_PRIORITY_CLASS; break;
1277 case 3: fFlags |= CREATE_SUSPENDED | NORMAL_PRIORITY_CLASS; break;
1278 case 4: fFlags |= CREATE_SUSPENDED | HIGH_PRIORITY_CLASS; break;
1279 case 5: fFlags |= CREATE_SUSPENDED | REALTIME_PRIORITY_CLASS; break;
1280 }
1281#endif
1282 if (g_cProcessorGroups > 1)
1283 fFlags |= CREATE_SUSPENDED;
1284
1285 /*
1286 * Try create the process.
1287 */
1288 DB(DB_JOBS, ("CreateProcessW(%ls, %ls,,, TRUE, %#x...)\n", pwszImageName, pwszCommandLine, fFlags));
1289 memset(&ProcInfo, 0, sizeof(ProcInfo));
1290#ifdef WITH_RW_LOCK
1291 AcquireSRWLockShared(&g_RWLock);
1292#endif
1293
1294 fRet = CreateProcessW((WCHAR *)pwszImageName, (WCHAR *)pwszCommandLine, NULL /*pProcSecAttr*/, NULL /*pThreadSecAttr*/,
1295 FALSE /*fInheritHandles*/, fFlags, (WCHAR *)pwszzEnvironment, pwszCwd, &StartupInfo, &ProcInfo);
1296 dwErr = GetLastError();
1297
1298#ifdef WITH_RW_LOCK
1299 ReleaseSRWLockShared(&g_RWLock);
1300#endif
1301 if (fRet)
1302 *phProcess = ProcInfo.hProcess;
1303 else
1304 {
1305 MkWinChildError(pWorker, 1, "CreateProcess(%ls) failed: %u\n", pwszImageName, dwErr);
1306 return (int)dwErr;
1307 }
1308
1309 /*
1310 * If the child is suspended, we've got some adjustment work to be done.
1311 */
1312 dwErr = ERROR_SUCCESS;
1313 if (fFlags & CREATE_SUSPENDED)
1314 {
1315 /*
1316 * First do handle inhertiance as that's the most complicated.
1317 */
1318 if (fHaveHandles || fCatchOutput)
1319 {
1320 char szErrMsg[128];
1321 if (fCatchOutput)
1322 {
1323 if (!pafReplace[1])
1324 {
1325 pafReplace[1] = TRUE;
1326 pahChild[1] = pWorker->pStdOut->hPipeChild;
1327 }
1328 if (!pafReplace[2])
1329 {
1330 pafReplace[2] = TRUE;
1331 pahChild[2] = pWorker->pStdErr->hPipeChild;
1332 }
1333 }
1334 dwErr = nt_child_inject_standard_handles(ProcInfo.hProcess, pafReplace, pahChild, szErrMsg, sizeof(szErrMsg));
1335 if (dwErr != 0)
1336 MkWinChildError(pWorker, 1, "%s\n", szErrMsg);
1337 }
1338
1339 /*
1340 * Assign processor group (ignore failure).
1341 */
1342#ifdef MKWINCHILD_DO_SET_PROCESSOR_GROUP
1343 if (g_cProcessorGroups > 1)
1344 {
1345 GROUP_AFFINITY Affinity = { 0 /* == all active apparently */, pWorker->iProcessorGroup, { 0, 0, 0 } };
1346 fRet = g_pfnSetThreadGroupAffinity(ProcInfo.hThread, &Affinity, NULL);
1347 assert(fRet);
1348 }
1349#endif
1350
1351#ifdef KMK
1352 /*
1353 * Set priority (ignore failure).
1354 */
1355 switch (process_priority)
1356 {
1357 case 1: fRet = SetThreadPriority(ProcInfo.hThread, THREAD_PRIORITY_IDLE); break;
1358 case 2: fRet = SetThreadPriority(ProcInfo.hThread, THREAD_PRIORITY_BELOW_NORMAL); break;
1359 case 3: fRet = SetThreadPriority(ProcInfo.hThread, THREAD_PRIORITY_NORMAL); break;
1360 case 4: fRet = SetThreadPriority(ProcInfo.hThread, THREAD_PRIORITY_HIGHEST); break;
1361 case 5: fRet = SetThreadPriority(ProcInfo.hThread, THREAD_PRIORITY_TIME_CRITICAL); break;
1362 default: fRet = TRUE;
1363 }
1364 assert(fRet);
1365#endif
1366
1367 /*
1368 * Inject the job object if we're in a non-killing mode, to postpone
1369 * the closing of the job object and maybe make it more useful.
1370 */
1371 if (win_job_object_no_kill && g_hJob)
1372 {
1373 HANDLE hWhatever = INVALID_HANDLE_VALUE;
1374 DuplicateHandle(GetCurrentProcess(), g_hJob, ProcInfo.hProcess, &hWhatever, GENERIC_ALL,
1375 TRUE /*bInheritHandle*/, DUPLICATE_SAME_ACCESS);
1376 }
1377
1378 /*
1379 * Resume the thread if the adjustments succeeded, otherwise kill it.
1380 */
1381 if (dwErr == ERROR_SUCCESS)
1382 {
1383 fRet = ResumeThread(ProcInfo.hThread);
1384 assert(fRet);
1385 if (!fRet)
1386 {
1387 dwErr = GetLastError();
1388 MkWinChildError(pWorker, 1, "ResumeThread failed on child process: %u\n", dwErr);
1389 }
1390 }
1391 if (dwErr != ERROR_SUCCESS)
1392 TerminateProcess(ProcInfo.hProcess, dwErr);
1393 }
1394
1395 /*
1396 * Close unnecessary handles and cache the image.
1397 */
1398 CloseHandle(ProcInfo.hThread);
1399 kmk_cache_exec_image_w(pwszImageName);
1400 return 0;
1401}
1402
1403/**
1404 * Converts a argument vector that has already been quoted correctly.
1405 *
1406 * The argument vector is typically the result of quote_argv().
1407 *
1408 * @returns 0 on success, non-zero on failure.
1409 * @param pWorker The childcare worker.
1410 * @param papszArgs The argument vector to convert.
1411 * @param ppwszCommandLine Where to return the command line.
1412 */
1413static int mkWinChildcareWorkerConvertQuotedArgvToCommandline(PWINCHILDCAREWORKER pWorker, char **papszArgs,
1414 WCHAR **ppwszCommandLine)
1415{
1416 WCHAR *pwszCmdLine;
1417 WCHAR *pwszDst;
1418
1419 /*
1420 * Calc length the converted length.
1421 */
1422 unsigned cwcNeeded = 1;
1423 unsigned i = 0;
1424 const char *pszSrc;
1425 while ((pszSrc = papszArgs[i]) != NULL)
1426 {
1427 int cwcThis = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszSrc, -1, NULL, 0);
1428 if (cwcThis > 0 || *pszSrc == '\0')
1429 cwcNeeded += cwcThis + 1;
1430 else
1431 {
1432 DWORD dwErr = GetLastError();
1433 MkWinChildError(pWorker, 1, _("MultiByteToWideChar failed to convert argv[%u] (%s): %u\n"), i, pszSrc, dwErr);
1434 return dwErr;
1435 }
1436 i++;
1437 }
1438
1439 /*
1440 * Allocate and do the conversion.
1441 */
1442 pwszCmdLine = pwszDst = (WCHAR *)xmalloc(cwcNeeded * sizeof(WCHAR));
1443 i = 0;
1444 while ((pszSrc = papszArgs[i]) != NULL)
1445 {
1446 int cwcThis;
1447 if (i > 0)
1448 {
1449 *pwszDst++ = ' ';
1450 cwcNeeded--;
1451 }
1452
1453 cwcThis = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszSrc, -1, pwszDst, cwcNeeded);
1454 if (!cwcThis && *pszSrc != '\0')
1455 {
1456 DWORD dwErr = GetLastError();
1457 MkWinChildError(pWorker, 1, _("MultiByteToWideChar failed to convert argv[%u] (%s): %u\n"), i, pszSrc, dwErr);
1458 free(pwszCmdLine);
1459 return dwErr;
1460 }
1461 if (cwcThis > 0 && pwszDst[cwcThis - 1] == '\0')
1462 cwcThis--;
1463 pwszDst += cwcThis;
1464 cwcNeeded -= cwcThis;
1465 i++;
1466 }
1467 *pwszDst++ = '\0';
1468
1469 *ppwszCommandLine = pwszCmdLine;
1470 return 0;
1471}
1472
1473
1474#define MKWCCWCMD_F_CYGWIN_SHELL 1
1475#define MKWCCWCMD_F_MKS_SHELL 2
1476#define MKWCCWCMD_F_HAVE_SH 4
1477#define MKWCCWCMD_F_HAVE_KASH_C 8 /**< kmk_ash -c "..." */
1478
1479/*
1480 * @param pWorker The childcare worker if on one, otherwise NULL.
1481 */
1482static int mkWinChildcareWorkerConvertCommandline(PWINCHILDCAREWORKER pWorker, char **papszArgs, unsigned fFlags,
1483 WCHAR **ppwszCommandLine)
1484{
1485 struct ARGINFO
1486 {
1487 size_t cchSrc;
1488 size_t cwcDst; /**< converted size w/o terminator. */
1489 size_t cwcDstExtra : 24; /**< Only set with fSlowly. */
1490 size_t fSlowly : 1;
1491 size_t fQuoteIt : 1;
1492 size_t fEndSlashes : 1; /**< if escapes needed for trailing backslashes. */
1493 size_t fExtraSpace : 1; /**< if kash -c "" needs an extra space before the quote. */
1494 } *paArgInfo;
1495 size_t cArgs;
1496 size_t i;
1497 size_t cwcNeeded;
1498 WCHAR *pwszDst;
1499 WCHAR *pwszCmdLine;
1500
1501 /*
1502 * Count them first so we can allocate an info array of the stack.
1503 */
1504 cArgs = 0;
1505 while (papszArgs[cArgs] != NULL)
1506 cArgs++;
1507 paArgInfo = (struct ARGINFO *)alloca(sizeof(paArgInfo[0]) * cArgs);
1508
1509 /*
1510 * Preprocess them and calculate the exact command line length.
1511 */
1512 cwcNeeded = 1;
1513 for (i = 0; i < cArgs; i++)
1514 {
1515 char *pszSrc = papszArgs[i];
1516 size_t cchSrc = strlen(pszSrc);
1517 paArgInfo[i].cchSrc = cchSrc;
1518 if (cchSrc == 0)
1519 {
1520 /* empty needs quoting. */
1521 paArgInfo[i].cwcDst = 2;
1522 paArgInfo[i].cwcDstExtra = 0;
1523 paArgInfo[i].fSlowly = 0;
1524 paArgInfo[i].fQuoteIt = 1;
1525 paArgInfo[i].fExtraSpace = 0;
1526 paArgInfo[i].fEndSlashes = 0;
1527 }
1528 else
1529 {
1530 const char *pszSpace = memchr(pszSrc, ' ', cchSrc);
1531 const char *pszTab = memchr(pszSrc, '\t', cchSrc);
1532 const char *pszDQuote = memchr(pszSrc, '"', cchSrc);
1533 const char *pszEscape = memchr(pszSrc, '\\', cchSrc);
1534 int cwcDst = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszSrc, cchSrc + 1, NULL, 0);
1535 if (cwcDst >= 0)
1536 --cwcDst;
1537 else
1538 {
1539 DWORD dwErr = GetLastError();
1540 MkWinChildError(pWorker, 1, _("MultiByteToWideChar failed to convert argv[%u] (%s): %u\n"), i, pszSrc, dwErr);
1541 return dwErr;
1542 }
1543#if 0
1544 if (!pszSpace && !pszTab && !pszDQuote && !pszEscape)
1545 {
1546 /* no special handling needed. */
1547 paArgInfo[i].cwcDst = cwcDst;
1548 paArgInfo[i].cwcDstExtra = 0;
1549 paArgInfo[i].fSlowly = 0;
1550 paArgInfo[i].fQuoteIt = 0;
1551 paArgInfo[i].fExtraSpace = 0;
1552 paArgInfo[i].fEndSlashes = 0;
1553 }
1554 else if (!pszDQuote && !pszEscape)
1555 {
1556 /* Just double quote it. */
1557 paArgInfo[i].cwcDst = cwcDst + 2;
1558 paArgInfo[i].cwcDstExtra = 0;
1559 paArgInfo[i].fSlowly = 0;
1560 paArgInfo[i].fQuoteIt = 1;
1561 paArgInfo[i].fExtraSpace = 0;
1562 paArgInfo[i].fEndSlashes = 0;
1563 }
1564 else
1565#endif
1566 {
1567 /* Complicated, need to scan the string to figure out what to do. */
1568 size_t cwcDstExtra;
1569 int cBackslashes;
1570 char ch;
1571
1572 paArgInfo[i].fQuoteIt = 0;
1573 paArgInfo[i].fSlowly = 1;
1574 paArgInfo[i].fExtraSpace = 0;
1575 paArgInfo[i].fEndSlashes = 0;
1576
1577 cwcDstExtra = 0;
1578 cBackslashes = 0;
1579 while ((ch = *pszSrc++) != '\0')
1580 {
1581 switch (ch)
1582 {
1583 default:
1584 cBackslashes = 0;
1585 break;
1586
1587 case '\\':
1588 cBackslashes++;
1589 break;
1590
1591 case '"':
1592 if (fFlags & (MKWCCWCMD_F_CYGWIN_SHELL | MKWCCWCMD_F_MKS_SHELL))
1593 cwcDstExtra += 1; /* just an extra '"' */
1594 else
1595 cwcDstExtra += 1 + cBackslashes; /* extra '\\' for the '"' and for each preceeding slash. */
1596 cBackslashes = 0;
1597 break;
1598
1599 case ' ':
1600 case '\t':
1601 if (!paArgInfo[i].fQuoteIt)
1602 {
1603 paArgInfo[i].fQuoteIt = 1;
1604 cwcDstExtra += 2;
1605 }
1606 cBackslashes = 0;
1607 break;
1608 }
1609 }
1610
1611 /* If we're quoting the argument and it ends with trailing '\\', it/they must be escaped. */
1612 if ( cBackslashes > 0
1613 && paArgInfo[i].fQuoteIt
1614 && !(fFlags & (MKWCCWCMD_F_CYGWIN_SHELL | MKWCCWCMD_F_MKS_SHELL)))
1615 {
1616 cwcDstExtra += cBackslashes;
1617 paArgInfo[i].fEndSlashes = 1;
1618 }
1619
1620 paArgInfo[i].cwcDst = cwcDst + cwcDstExtra;
1621 paArgInfo[i].cwcDstExtra = cwcDstExtra;
1622 }
1623 }
1624
1625 if ( (fFlags & MKWCCWCMD_F_HAVE_KASH_C)
1626 && paArgInfo[i].fQuoteIt)
1627 {
1628 paArgInfo[i].fExtraSpace = 1;
1629 paArgInfo[i].cwcDst++;
1630 paArgInfo[i].cwcDstExtra++;
1631 }
1632
1633 cwcNeeded += (i != 0) + paArgInfo[i].cwcDst;
1634 }
1635
1636 /*
1637 * Allocate the result buffer and do the actual conversion.
1638 */
1639 pwszDst = pwszCmdLine = (WCHAR *)xmalloc(sizeof(WCHAR) * cwcNeeded);
1640 for (i = 0; i < cArgs; i++)
1641 {
1642 char *pszSrc = papszArgs[i];
1643 size_t cwcDst = paArgInfo[i].cwcDst;
1644
1645 if (i != 0)
1646 *pwszDst++ = L' ';
1647
1648 if (paArgInfo[i].fQuoteIt)
1649 {
1650 *pwszDst++ = L'"';
1651 cwcDst -= 2;
1652 }
1653
1654 if (!paArgInfo[i].fSlowly)
1655 {
1656 int cwcDst2 = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszSrc, paArgInfo[i].cchSrc, pwszDst, cwcDst + 1);
1657 assert(cwcDst2 >= 0);
1658 pwszDst += cwcDst;
1659 }
1660 else
1661 {
1662 /* Do the conversion into the end of the output buffer, then move
1663 it up to where it should be char by char. */
1664 int cBackslashes;
1665 size_t cwcLeft = paArgInfo[i].cwcDst - paArgInfo[i].cwcDstExtra;
1666 WCHAR volatile *pwchSlowSrc = pwszDst + paArgInfo[i].cwcDstExtra;
1667 WCHAR volatile *pwchSlowDst = pwszDst;
1668 int cwcDst2 = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszSrc, paArgInfo[i].cchSrc,
1669 (WCHAR *)pwchSlowSrc, cwcLeft + 1);
1670 assert(cwcDst2 >= 0);
1671
1672 cBackslashes = 0;
1673 while (cwcLeft-- > 0)
1674 {
1675 WCHAR wcSrc = *pwchSlowSrc++;
1676 if (wcSrc != L'\\' && wcSrc != L'"')
1677 cBackslashes = 0;
1678 else if (wcSrc == L'\\')
1679 cBackslashes++;
1680 else if ( (fFlags & (MKWCCWCMD_F_CYGWIN_SHELL | MKWCCWCMD_F_HAVE_SH))
1681 == (MKWCCWCMD_F_CYGWIN_SHELL | MKWCCWCMD_F_HAVE_SH))
1682 {
1683 *pwchSlowDst++ = L'"'; /* cygwin: '"' instead of '\\', no escaped slashes. */
1684 cBackslashes = 0;
1685 }
1686 else
1687 {
1688 if (!(fFlags & (MKWCCWCMD_F_CYGWIN_SHELL | MKWCCWCMD_F_MKS_SHELL)))
1689 cBackslashes += 1; /* one extra escape the '"' and one for each preceeding slash. */
1690 while (cBackslashes > 0)
1691 {
1692 *pwchSlowDst++ = L'\\';
1693 cBackslashes--;
1694 }
1695 }
1696 *pwchSlowDst++ = wcSrc;
1697 assert((uintptr_t)pwchSlowDst <= (uintptr_t)pwchSlowSrc);
1698 }
1699
1700 if (paArgInfo[i].fEndSlashes)
1701 while (cBackslashes-- > 0)
1702 *pwchSlowDst++ = L'\\';
1703
1704 pwszDst += cwcDst;
1705 assert(pwszDst == (WCHAR *)pwchSlowDst);
1706 }
1707
1708 if (paArgInfo[i].fExtraSpace)
1709 *pwszDst++ = L' ';
1710 if (paArgInfo[i].fQuoteIt)
1711 *pwszDst++ = L'"';
1712 }
1713 *pwszDst = L'\0';
1714 *ppwszCommandLine = pwszCmdLine;
1715 return 0;
1716}
1717
1718static int mkWinChildcareWorkerConvertCommandlineWithShell(PWINCHILDCAREWORKER pWorker, const WCHAR *pwszShell, char **papszArgs,
1719 WCHAR **ppwszCommandLine)
1720{
1721 MkWinChildError(pWorker, 1, "%s: not found!\n", papszArgs[0]);
1722//__debugbreak();
1723 return ERROR_FILE_NOT_FOUND;
1724}
1725
1726/**
1727 * Searches the environment block for the PATH variable.
1728 *
1729 * @returns Pointer to the path in the block or "." in pwszPathFallback.
1730 * @param pwszzEnv The UTF-16 environment block to search.
1731 * @param pwszPathFallback Fallback.
1732 */
1733static const WCHAR *mkWinChildcareWorkerFindPathValue(const WCHAR *pwszzEnv, WCHAR pwszPathFallback[4])
1734{
1735 while (*pwszzEnv)
1736 {
1737 size_t cwcVar = wcslen(pwszzEnv);
1738 if (!IS_PATH_ENV_VAR(cwcVar, pwszzEnv))
1739 pwszzEnv += cwcVar + 1;
1740 else if (cwcVar > 5)
1741 return &pwszzEnv[5];
1742 else
1743 break;
1744 }
1745 pwszPathFallback[0] = L'.';
1746 pwszPathFallback[1] = L'\0';
1747 return pwszPathFallback;
1748}
1749
1750/**
1751 * Checks if we need to had this executable file to the shell.
1752 *
1753 * @returns TRUE if it's shell fooder, FALSE if we think windows can handle it.
1754 * @param hFile Handle to the file in question
1755 */
1756static BOOL mkWinChildcareWorkerCheckIfNeedShell(HANDLE hFile)
1757{
1758 /*
1759 * Read the first 512 bytes and check for an executable image header.
1760 */
1761 union
1762 {
1763 DWORD dwSignature;
1764 WORD wSignature;
1765 BYTE ab[128];
1766 } uBuf;
1767 DWORD cbRead;
1768 uBuf.dwSignature = 0;
1769 if ( ReadFile(hFile, &uBuf, sizeof(uBuf), &cbRead, NULL /*pOverlapped*/)
1770 && cbRead == sizeof(uBuf))
1771 {
1772 if (uBuf.wSignature == IMAGE_DOS_SIGNATURE)
1773 return FALSE;
1774 if (uBuf.dwSignature == IMAGE_NT_SIGNATURE)
1775 return FALSE;
1776 if ( uBuf.wSignature == IMAGE_OS2_SIGNATURE /* NE */
1777 || uBuf.wSignature == 0x5d4c /* LX */
1778 || uBuf.wSignature == IMAGE_OS2_SIGNATURE_LE /* LE */)
1779 return FALSE;
1780 }
1781 return TRUE;
1782}
1783
1784/**
1785 * Checks if the image path looks like microsoft CL.exe.
1786 *
1787 * @returns TRUE / FALSE.
1788 * @param pwszImagePath The executable image path to evalutate.
1789 * @param cwcImagePath The length of the image path.
1790 */
1791static BOOL mkWinChildIsProbableClExe(WCHAR const *pwszImagePath, size_t cwcImagePath)
1792{
1793 assert(pwszImagePath[cwcImagePath] == '\0');
1794 return cwcImagePath > 7
1795 && (pwszImagePath[cwcImagePath - 7] == L'/' || pwszImagePath[cwcImagePath - 7] == L'\\')
1796 && (pwszImagePath[cwcImagePath - 6] == L'c' || pwszImagePath[cwcImagePath - 6] == L'C')
1797 && (pwszImagePath[cwcImagePath - 5] == L'l' || pwszImagePath[cwcImagePath - 5] == L'L')
1798 && pwszImagePath[cwcImagePath - 4] == L'.'
1799 && (pwszImagePath[cwcImagePath - 3] == L'e' || pwszImagePath[cwcImagePath - 3] == L'E')
1800 && (pwszImagePath[cwcImagePath - 2] == L'x' || pwszImagePath[cwcImagePath - 2] == L'X')
1801 && (pwszImagePath[cwcImagePath - 1] == L'e' || pwszImagePath[cwcImagePath - 1] == L'E');
1802}
1803
1804/**
1805 * Temporary workaround for seemingly buggy kFsCache.c / dir-nt-bird.c.
1806 *
1807 * Something is not invalidated / updated correctly!
1808 */
1809static BOOL mkWinChildcareWorkerIsRegularFileW(PWINCHILDCAREWORKER pWorker, wchar_t const *pwszPath)
1810{
1811 BOOL fRet = FALSE;
1812#ifdef KMK
1813 if (utf16_regular_file_p(pwszPath))
1814 fRet = TRUE;
1815 else
1816#endif
1817 {
1818 /* Don't believe the cache. */
1819 DWORD dwAttr = GetFileAttributesW(pwszPath);
1820 if (dwAttr != INVALID_FILE_ATTRIBUTES)
1821 {
1822 if (!(dwAttr & FILE_ATTRIBUTE_DIRECTORY))
1823 {
1824#ifdef KMK
1825 extern void dir_cache_invalid_volatile(void);
1826 dir_cache_invalid_volatile();
1827 if (utf16_regular_file_p(pwszPath))
1828 MkWinChildError(pWorker, 1, "kFsCache was out of sync! pwszPath=%S\n", pwszPath);
1829 else
1830 {
1831 dir_cache_invalid_all();
1832 if (utf16_regular_file_p(pwszPath))
1833 MkWinChildError(pWorker, 1, "kFsCache was really out of sync! pwszPath=%S\n", pwszPath);
1834 else
1835 MkWinChildError(pWorker, 1, "kFsCache is really out of sync!! pwszPath=%S\n", pwszPath);
1836 }
1837#endif
1838 fRet = TRUE;
1839 }
1840 }
1841 }
1842 return fRet;
1843}
1844
1845
1846/**
1847 * Tries to locate the image file, searching the path and maybe falling back on
1848 * the shell in case it knows more (think cygwin with its own view of the file
1849 * system).
1850 *
1851 * This will also check for shell script, falling back on the shell too to
1852 * handle those.
1853 *
1854 * @returns 0 on success, windows error code on failure.
1855 * @param pWorker The childcare worker.
1856 * @param pszArg0 The first argument.
1857 * @param pwszSearchPath In case mkWinChildcareWorkerConvertEnvironment
1858 * had a chance of locating the search path already.
1859 * @param pwszzEnv The environment block, in case we need to look for
1860 * the path.
1861 * @param pszShell The shell.
1862 * @param ppwszImagePath Where to return the pointer to the image path. This
1863 * could be the shell.
1864 * @param pfNeedShell Where to return shell vs direct execution indicator.
1865 * @param pfProbableClExe Where to return an indicator of probably CL.EXE.
1866 */
1867static int mkWinChildcareWorkerFindImage(PWINCHILDCAREWORKER pWorker, char const *pszArg0, WCHAR *pwszSearchPath,
1868 WCHAR const *pwszzEnv, const char *pszShell,
1869 WCHAR **ppwszImagePath, BOOL *pfNeedShell, BOOL *pfProbableClExe)
1870{
1871 /** @todo Slap a cache on this code. We usually end up executing the same
1872 * stuff over and over again (e.g. compilers, linkers, etc).
1873 * Hitting the file system is slow on windows. */
1874
1875 /*
1876 * Convert pszArg0 to unicode so we can work directly on that.
1877 */
1878 WCHAR wszArg0[MKWINCHILD_MAX_PATH + 4]; /* +4 for painless '.exe' appending */
1879 DWORD dwErr;
1880 size_t cbArg0 = strlen(pszArg0) + 1;
1881 int const cwcArg0 = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszArg0, cbArg0, wszArg0, MKWINCHILD_MAX_PATH);
1882 if (cwcArg0 > 0)
1883 {
1884 HANDLE hFile = INVALID_HANDLE_VALUE;
1885 WCHAR wszPathBuf[MKWINCHILD_MAX_PATH + 4]; /* +4 for painless '.exe' appending */
1886 int cwc;
1887
1888 /*
1889 * If there isn't an .exe suffix, we may have to add one.
1890 * Also we ASSUME that .exe suffixes means no hash bang detection needed.
1891 */
1892 int const fHasExeSuffix = cwcArg0 > CSTRLEN(".exe")
1893 && wszArg0[cwcArg0 - 4] == '.'
1894 && (wszArg0[cwcArg0 - 3] == L'e' || wszArg0[cwcArg0 - 3] == L'E')
1895 && (wszArg0[cwcArg0 - 2] == L'x' || wszArg0[cwcArg0 - 2] == L'X')
1896 && (wszArg0[cwcArg0 - 1] == L'e' || wszArg0[cwcArg0 - 1] == L'E');
1897
1898 /*
1899 * If there isn't any path specified, we need to search the PATH env.var.
1900 */
1901 int const fHasPath = wszArg0[1] == L':'
1902 || wszArg0[0] == L'\\'
1903 || wszArg0[0] == L'/'
1904 || wmemchr(wszArg0, L'/', cwcArg0)
1905 || wmemchr(wszArg0, L'\\', cwcArg0);
1906
1907 /* Before we do anything, flip UNIX slashes to DOS ones. */
1908 WCHAR *pwc = wszArg0;
1909 while ((pwc = wcschr(pwc, L'/')) != NULL)
1910 *pwc++ = L'\\';
1911
1912 /* Don't need to set these all the time... */
1913 *pfNeedShell = FALSE;
1914 *pfProbableClExe = FALSE;
1915
1916 /*
1917 * If any kind of path is specified in arg0, we will not search the
1918 * PATH env.var and can limit ourselves to maybe slapping a .exe on to it.
1919 */
1920 if (fHasPath)
1921 {
1922 /*
1923 * If relative to a CWD, turn it into an absolute one.
1924 */
1925 unsigned cwcPath = cwcArg0;
1926 WCHAR *pwszPath = wszArg0;
1927 if ( *pwszPath != L'\\'
1928 && (pwszPath[1] != ':' || pwszPath[2] != L'\\') )
1929 {
1930 DWORD cwcAbsPath = GetFullPathNameW(wszArg0, MKWINCHILD_MAX_PATH, wszPathBuf, NULL);
1931 if (cwcAbsPath > 0)
1932 {
1933 cwcPath = cwcAbsPath + 1; /* include terminator, like MultiByteToWideChar does. */
1934 pwszPath = wszPathBuf;
1935 }
1936 }
1937
1938 /*
1939 * Check with .exe suffix first.
1940 * We don't open .exe files and look for hash bang stuff, we just
1941 * assume they are executable images that CreateProcess can deal with.
1942 */
1943 if (!fHasExeSuffix)
1944 {
1945 pwszPath[cwcPath - 1] = L'.';
1946 pwszPath[cwcPath ] = L'e';
1947 pwszPath[cwcPath + 1] = L'x';
1948 pwszPath[cwcPath + 2] = L'e';
1949 pwszPath[cwcPath + 3] = L'\0';
1950 }
1951
1952 if (mkWinChildcareWorkerIsRegularFileW(pWorker, pwszPath))
1953 {
1954 *pfProbableClExe = mkWinChildIsProbableClExe(pwszPath, cwcPath + 4 - 1);
1955 return mkWinChildDuplicateUtf16String(pwszPath, cwcPath + 4, ppwszImagePath);
1956 }
1957
1958 /*
1959 * If no suffix was specified, try without .exe too, but now we need
1960 * to see if it's for the shell or CreateProcess.
1961 */
1962 if (!fHasExeSuffix)
1963 {
1964 pwszPath[cwcPath - 1] = L'\0';
1965#ifdef KMK
1966 if (mkWinChildcareWorkerIsRegularFileW(pWorker, pwszPath))
1967#endif
1968 {
1969 hFile = CreateFileW(pwszPath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
1970 NULL /*pSecAttr*/, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1971 if (hFile != INVALID_HANDLE_VALUE)
1972 {
1973 *pfNeedShell = mkWinChildcareWorkerCheckIfNeedShell(hFile);
1974 CloseHandle(hFile);
1975 if (!*pfNeedShell)
1976 {
1977 *pfProbableClExe = mkWinChildIsProbableClExe(pwszPath, cwcPath - 1);
1978 return mkWinChildDuplicateUtf16String(pwszPath, cwcPath, ppwszImagePath);
1979 }
1980 }
1981 }
1982 }
1983 }
1984 /*
1985 * No path, need to search the PATH env.var. for the executable, maybe
1986 * adding an .exe suffix while do so if that is missing.
1987 */
1988 else
1989 {
1990 BOOL fSearchedCwd = FALSE;
1991 WCHAR wszPathFallback[4];
1992 if (!pwszSearchPath)
1993 pwszSearchPath = (WCHAR *)mkWinChildcareWorkerFindPathValue(pwszzEnv, wszPathFallback);
1994
1995 for (;;)
1996 {
1997 size_t cwcCombined;
1998
1999 /*
2000 * Find the end of the current PATH component.
2001 */
2002 size_t cwcSkip;
2003 WCHAR wcEnd;
2004 size_t cwcComponent = 0;
2005 WCHAR wc;
2006 while ((wc = pwszSearchPath[cwcComponent]) != L'\0')
2007 {
2008 if (wc != ';' && wc != ':')
2009 { /* likely */ }
2010 else if (wc == ';')
2011 break;
2012 else if (cwcComponent != (pwszSearchPath[cwcComponent] != L'"' ? 1 : 2))
2013 break;
2014 cwcComponent++;
2015 }
2016 wcEnd = wc;
2017
2018 /* Trim leading spaces and double quotes. */
2019 while ( cwcComponent > 0
2020 && ((wc = *pwszSearchPath) == L'"' || wc == L' ' || wc == L'\t'))
2021 {
2022 pwszSearchPath++;
2023 cwcComponent--;
2024 }
2025 cwcSkip = cwcComponent;
2026
2027 /* Trim trailing spaces & double quotes. */
2028 while ( cwcComponent > 0
2029 && ((wc = pwszSearchPath[cwcComponent - 1]) == L'"' || wc == L' ' || wc == L'\t'))
2030 cwcComponent--;
2031
2032 /*
2033 * Skip empty components. Join the component and the filename, making sure to
2034 * resolve any CWD relative stuff first.
2035 */
2036 cwcCombined = cwcComponent + 1 + cwcArg0;
2037 if (cwcComponent > 0 && cwcCombined <= MKWINCHILD_MAX_PATH)
2038 {
2039 /* Copy the component into wszPathBuf, maybe abspath'ing it. */
2040 DWORD cwcAbsPath = 0;
2041 if ( *pwszSearchPath != L'\\'
2042 && (pwszSearchPath[1] != ':' || pwszSearchPath[2] != L'\\') )
2043 {
2044 /* To save an extra buffer + copying, we'll temporarily modify the PATH
2045 value in our converted UTF-16 environment block. */
2046 WCHAR const wcSaved = pwszSearchPath[cwcComponent];
2047 pwszSearchPath[cwcComponent] = L'\0';
2048 cwcAbsPath = GetFullPathNameW(pwszSearchPath, MKWINCHILD_MAX_PATH, wszPathBuf, NULL);
2049 pwszSearchPath[cwcComponent] = wcSaved;
2050 if (cwcAbsPath > 0 && cwcAbsPath + 1 + cwcArg0 <= MKWINCHILD_MAX_PATH)
2051 cwcCombined = cwcAbsPath + 1 + cwcArg0;
2052 else
2053 cwcAbsPath = 0;
2054 }
2055 if (cwcAbsPath == 0)
2056 {
2057 memcpy(wszPathBuf, pwszSearchPath, cwcComponent * sizeof(WCHAR));
2058 cwcAbsPath = cwcComponent;
2059 }
2060
2061 /* Append the filename. */
2062 if ((wc = wszPathBuf[cwcAbsPath - 1]) == L'\\' || wc == L'/' || wc == L':')
2063 {
2064 memcpy(&wszPathBuf[cwcAbsPath], wszArg0, cwcArg0 * sizeof(WCHAR));
2065 cwcCombined--;
2066 }
2067 else
2068 {
2069 wszPathBuf[cwcAbsPath] = L'\\';
2070 memcpy(&wszPathBuf[cwcAbsPath + 1], wszArg0, cwcArg0 * sizeof(WCHAR));
2071 }
2072 assert(wszPathBuf[cwcCombined - 1] == L'\0');
2073
2074 /* DOS slash conversion */
2075 pwc = wszPathBuf;
2076 while ((pwc = wcschr(pwc, L'/')) != NULL)
2077 *pwc++ = L'\\';
2078
2079 /*
2080 * Search with exe suffix first.
2081 */
2082 if (!fHasExeSuffix)
2083 {
2084 wszPathBuf[cwcCombined - 1] = L'.';
2085 wszPathBuf[cwcCombined ] = L'e';
2086 wszPathBuf[cwcCombined + 1] = L'x';
2087 wszPathBuf[cwcCombined + 2] = L'e';
2088 wszPathBuf[cwcCombined + 3] = L'\0';
2089 }
2090 if (mkWinChildcareWorkerIsRegularFileW(pWorker, wszPathBuf))
2091 {
2092 *pfProbableClExe = mkWinChildIsProbableClExe(wszPathBuf, cwcCombined + (fHasExeSuffix ? 0 : 4) - 1);
2093 return mkWinChildDuplicateUtf16String(wszPathBuf, cwcCombined + (fHasExeSuffix ? 0 : 4), ppwszImagePath);
2094 }
2095 if (!fHasExeSuffix)
2096 {
2097 wszPathBuf[cwcCombined - 1] = L'\0';
2098#ifdef KMK
2099 if (mkWinChildcareWorkerIsRegularFileW(pWorker, wszPathBuf))
2100#endif
2101 {
2102 /*
2103 * Check if the file exists w/o the added '.exe' suffix. If it does,
2104 * we need to check if we can pass it to CreateProcess or need the shell.
2105 */
2106 hFile = CreateFileW(wszPathBuf, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
2107 NULL /*pSecAttr*/, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
2108 if (hFile != INVALID_HANDLE_VALUE)
2109 {
2110 *pfNeedShell = mkWinChildcareWorkerCheckIfNeedShell(hFile);
2111 CloseHandle(hFile);
2112 if (!*pfNeedShell)
2113 {
2114 *pfProbableClExe = mkWinChildIsProbableClExe(wszPathBuf, cwcCombined - 1);
2115 return mkWinChildDuplicateUtf16String(wszPathBuf, cwcCombined, ppwszImagePath);
2116 }
2117 break;
2118 }
2119 }
2120 }
2121 }
2122
2123 /*
2124 * Advance to the next component.
2125 */
2126 if (wcEnd != '\0')
2127 pwszSearchPath += cwcSkip + 1;
2128 else if (fSearchedCwd)
2129 break;
2130 else
2131 {
2132 fSearchedCwd = TRUE;
2133 wszPathFallback[0] = L'.';
2134 wszPathFallback[1] = L'\0';
2135 pwszSearchPath = wszPathFallback;
2136 }
2137 }
2138 }
2139
2140 /*
2141 * We need the shell. It will take care of finding/reporting missing
2142 * image files and such.
2143 */
2144 *pfNeedShell = TRUE;
2145 if (pszShell)
2146 {
2147 cwc = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszShell, strlen(pszShell) + 1, wszPathBuf, MKWINCHILD_MAX_PATH);
2148 if (cwc > 0)
2149 return mkWinChildDuplicateUtf16String(wszPathBuf, cwc, ppwszImagePath);
2150 dwErr = GetLastError();
2151 MkWinChildError(pWorker, 1, _("MultiByteToWideChar failed to convert shell (%s): %u\n"), pszShell, dwErr);
2152 }
2153 else
2154 {
2155 MkWinChildError(pWorker, 1, "%s: not found!\n", pszArg0);
2156 dwErr = ERROR_FILE_NOT_FOUND;
2157 }
2158 }
2159 else
2160 {
2161 dwErr = GetLastError();
2162 MkWinChildError(pWorker, 1, _("MultiByteToWideChar failed to convert argv[0] (%s): %u\n"), pszArg0, dwErr);
2163 }
2164 return dwErr == ERROR_INSUFFICIENT_BUFFER ? ERROR_FILENAME_EXCED_RANGE : dwErr;
2165}
2166
2167/**
2168 * Creates the environment block.
2169 *
2170 * @returns 0 on success, windows error code on failure.
2171 * @param pWorker The childcare worker if on one, otherwise NULL.
2172 * @param papszEnv The environment vector to convert.
2173 * @param cbEnvStrings The size of the environment strings, iff they are
2174 * sequential in a block. Otherwise, zero.
2175 * @param ppwszEnv Where to return the pointer to the environment
2176 * block.
2177 * @param ppwszSearchPath Where to return the pointer to the path value
2178 * within the environment block. This will not be set
2179 * if cbEnvStrings is non-zero, more efficient to let
2180 * mkWinChildcareWorkerFindImage() search when needed.
2181 */
2182static int mkWinChildcareWorkerConvertEnvironment(PWINCHILDCAREWORKER pWorker, char **papszEnv, size_t cbEnvStrings,
2183 WCHAR **ppwszEnv, WCHAR const **ppwszSearchPath)
2184{
2185 DWORD dwErr;
2186 int cwcRc;
2187 int cwcDst;
2188 WCHAR *pwszzDst;
2189
2190 *ppwszSearchPath = NULL;
2191
2192 /*
2193 * We've got a little optimization here with help from mkWinChildCopyStringArray.
2194 */
2195 if (cbEnvStrings)
2196 {
2197 cwcDst = cbEnvStrings + 32;
2198 pwszzDst = (WCHAR *)xmalloc(cwcDst * sizeof(WCHAR));
2199 cwcRc = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, papszEnv[0], cbEnvStrings, pwszzDst, cwcDst);
2200 if (cwcRc != 0)
2201 {
2202 *ppwszEnv = pwszzDst;
2203 return 0;
2204 }
2205
2206 /* Resize the allocation and try again. */
2207 dwErr = GetLastError();
2208 if (dwErr == ERROR_INSUFFICIENT_BUFFER)
2209 {
2210 cwcRc = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, papszEnv[0], cbEnvStrings, NULL, 0);
2211 if (cwcRc > 0)
2212 cwcDst = cwcRc + 32;
2213 else
2214 cwcDst *= 2;
2215 pwszzDst = (WCHAR *)xrealloc(pwszzDst, cwcDst);
2216 cwcRc = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, papszEnv[0], cbEnvStrings, pwszzDst, cwcDst);
2217 if (cwcRc != 0)
2218 {
2219 *ppwszEnv = pwszzDst;
2220 return 0;
2221 }
2222 dwErr = GetLastError();
2223 }
2224 MkWinChildError(pWorker, 1, _("MultiByteToWideChar failed to convert environment block: %u\n"), dwErr);
2225 }
2226 /*
2227 * Need to convert it string by string.
2228 */
2229 else
2230 {
2231 size_t offPathValue = ~(size_t)0;
2232 size_t offDst;
2233
2234 /*
2235 * Estimate the size first.
2236 */
2237 size_t cEnvVars;
2238 size_t cwcDst = 32;
2239 size_t iVar = 0;
2240 const char *pszSrc;
2241 while ((pszSrc = papszEnv[iVar]) != NULL)
2242 {
2243 cwcDst += strlen(pszSrc) + 1;
2244 iVar++;
2245 }
2246 cEnvVars = iVar;
2247
2248 /* Allocate estimated WCHARs and convert the variables one by one, reallocating
2249 the block as needed. */
2250 pwszzDst = (WCHAR *)xmalloc(cwcDst * sizeof(WCHAR));
2251 cwcDst--; /* save one wchar for the terminating empty string. */
2252 offDst = 0;
2253 for (iVar = 0; iVar < cEnvVars; iVar++)
2254 {
2255 size_t cwcLeft = cwcDst - offDst;
2256 size_t const cbSrc = strlen(pszSrc = papszEnv[iVar]) + 1;
2257 assert(cwcDst >= offDst);
2258
2259
2260 cwcRc = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszSrc, cbSrc, &pwszzDst[offDst], cwcLeft);
2261 if (cwcRc > 0)
2262 { /* likely */ }
2263 else
2264 {
2265 dwErr = GetLastError();
2266 if (dwErr == ERROR_INSUFFICIENT_BUFFER)
2267 {
2268 /* Need more space. So, calc exacly how much and resize the block accordingly. */
2269 size_t cbSrc2 = cbSrc;
2270 size_t iVar2 = iVar;
2271 cwcLeft = 1;
2272 for (;;)
2273 {
2274 size_t cwcRc2 = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszSrc, cbSrc, NULL, 0);
2275 if (cwcRc2 > 0)
2276 cwcLeft += cwcRc2;
2277 else
2278 cwcLeft += cbSrc * 4;
2279
2280 /* advance */
2281 iVar2++;
2282 if (iVar2 >= cEnvVars)
2283 break;
2284 pszSrc = papszEnv[iVar2];
2285 cbSrc2 = strlen(pszSrc) + 1;
2286 }
2287 pszSrc = papszEnv[iVar];
2288
2289 /* Grow the allocation and repeat the conversion. */
2290 if (offDst + cwcLeft > cwcDst + 1)
2291 {
2292 cwcDst = offDst + cwcLeft;
2293 pwszzDst = (WCHAR *)xrealloc(pwszzDst, cwcDst * sizeof(WCHAR));
2294 cwcDst--; /* save one wchar for the terminating empty string. */
2295 cwcRc = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszSrc, cbSrc, &pwszzDst[offDst], cwcLeft - 1);
2296 if (cwcRc <= 0)
2297 dwErr = GetLastError();
2298 }
2299 }
2300 if (cwcRc <= 0)
2301 {
2302 MkWinChildError(pWorker, 1, _("MultiByteToWideChar failed to convert environment string #%u (%s): %u\n"),
2303 iVar, pszSrc, dwErr);
2304 free(pwszzDst);
2305 return dwErr;
2306 }
2307 }
2308
2309 /* Look for the PATH. */
2310 if ( offPathValue == ~(size_t)0
2311 && IS_PATH_ENV_VAR(cwcRc, &pwszzDst[offDst]) )
2312 offPathValue = offDst + 4 + 1;
2313
2314 /* Advance. */
2315 offDst += cwcRc;
2316 }
2317 pwszzDst[offDst++] = '\0';
2318
2319 if (offPathValue != ~(size_t)0)
2320 *ppwszSearchPath = &pwszzDst[offPathValue];
2321 *ppwszEnv = pwszzDst;
2322 return 0;
2323 }
2324 free(pwszzDst);
2325 return dwErr;
2326}
2327
2328/**
2329 * Childcare worker: handle regular process.
2330 *
2331 * @param pWorker The worker.
2332 * @param pChild The kSubmit child.
2333 */
2334static void mkWinChildcareWorkerThreadHandleProcess(PWINCHILDCAREWORKER pWorker, PWINCHILD pChild)
2335{
2336 WCHAR *pwszSearchPath = NULL;
2337 WCHAR *pwszzEnvironment = NULL;
2338 WCHAR *pwszCommandLine = NULL;
2339 WCHAR *pwszImageName = NULL;
2340 BOOL fNeedShell = FALSE;
2341 BOOL fProbableClExe = FALSE;
2342 int rc;
2343
2344 /*
2345 * First we convert the environment so we get the PATH we need to
2346 * search for the executable.
2347 */
2348 rc = mkWinChildcareWorkerConvertEnvironment(pWorker, pChild->u.Process.papszEnv ? pChild->u.Process.papszEnv : environ,
2349 pChild->u.Process.cbEnvStrings,
2350 &pwszzEnvironment, &pwszSearchPath);
2351 /*
2352 * Find the executable and maybe checking if it's a shell script, then
2353 * convert it to a command line.
2354 */
2355 if (rc == 0)
2356 rc = mkWinChildcareWorkerFindImage(pWorker, pChild->u.Process.papszArgs[0], pwszSearchPath, pwszzEnvironment,
2357 pChild->u.Process.pszShell, &pwszImageName, &fNeedShell, &pChild->fProbableClExe);
2358 if (rc == 0)
2359 {
2360 if (!fNeedShell)
2361 rc = mkWinChildcareWorkerConvertCommandline(pWorker, pChild->u.Process.papszArgs, 0 /*fFlags*/, &pwszCommandLine);
2362 else
2363 rc = mkWinChildcareWorkerConvertCommandlineWithShell(pWorker, pwszImageName, pChild->u.Process.papszArgs,
2364 &pwszCommandLine);
2365
2366 /*
2367 * Create the child process.
2368 */
2369 if (rc == 0)
2370 {
2371 BOOL afReplace[3] = { FALSE, pChild->u.Process.hStdOut != INVALID_HANDLE_VALUE, pChild->u.Process.hStdErr != INVALID_HANDLE_VALUE };
2372 HANDLE ahChild[3] = { INVALID_HANDLE_VALUE, pChild->u.Process.hStdOut, pChild->u.Process.hStdErr };
2373 rc = mkWinChildcareWorkerCreateProcess(pWorker, pwszImageName, pwszCommandLine, pwszzEnvironment,
2374 NULL /*pwszCwd*/, afReplace, ahChild, pChild->u.Process.fCatchOutput,
2375 &pChild->u.Process.hProcess);
2376 mkWinChildcareWorkerCloseStandardHandles(pChild);
2377 if (rc == 0)
2378 {
2379 /*
2380 * Wait for the child to complete.
2381 */
2382 mkWinChildcareWorkerWaitForProcess(pWorker, pChild, pChild->u.Process.hProcess, pwszImageName,
2383 pChild->u.Process.fCatchOutput);
2384 }
2385 else
2386 pChild->iExitCode = rc;
2387 }
2388 else
2389 pChild->iExitCode = rc;
2390 }
2391 else
2392 pChild->iExitCode = rc;
2393 free(pwszCommandLine);
2394 free(pwszImageName);
2395 free(pwszzEnvironment);
2396
2397 /* In case we failed, we must make sure the child end of pipes
2398 used by $(shell no_such_command.exe) are closed, otherwise
2399 the main thread will be stuck reading the parent end. */
2400 mkWinChildcareWorkerCloseStandardHandles(pChild);
2401}
2402
2403#ifdef KMK
2404
2405/**
2406 * Childcare worker: handle builtin command.
2407 *
2408 * @param pWorker The worker.
2409 * @param pChild The kSubmit child.
2410 */
2411static void mkWinChildcareWorkerThreadHandleBuiltIn(PWINCHILDCAREWORKER pWorker, PWINCHILD pChild)
2412{
2413 PCKMKBUILTINENTRY pBuiltIn = pChild->u.BuiltIn.pBuiltIn;
2414 KMKBUILTINCTX Ctx =
2415 {
2416 pBuiltIn->uName.s.sz,
2417 pChild->pMkChild ? &pChild->pMkChild->output : NULL,
2418 pWorker,
2419 };
2420 if (pBuiltIn->uFnSignature == FN_SIG_MAIN)
2421 pChild->iExitCode = pBuiltIn->u.pfnMain(pChild->u.BuiltIn.cArgs, pChild->u.BuiltIn.papszArgs,
2422 pChild->u.BuiltIn.papszEnv, &Ctx);
2423 else if (pBuiltIn->uFnSignature == FN_SIG_MAIN_SPAWNS)
2424 pChild->iExitCode = pBuiltIn->u.pfnMainSpawns(pChild->u.BuiltIn.cArgs, pChild->u.BuiltIn.papszArgs,
2425 pChild->u.BuiltIn.papszEnv, &Ctx, pChild->pMkChild, NULL /*pPid*/);
2426 else
2427 {
2428 assert(0);
2429 pChild->iExitCode = 98;
2430 }
2431}
2432
2433/**
2434 * Childcare worker: handle append write-out.
2435 *
2436 * @param pWorker The worker.
2437 * @param pChild The kSubmit child.
2438 */
2439static void mkWinChildcareWorkerThreadHandleAppend(PWINCHILDCAREWORKER pWorker, PWINCHILD pChild)
2440{
2441 int fd = open(pChild->u.Append.pszFilename,
2442 pChild->u.Append.fTruncate
2443 ? O_WRONLY | O_TRUNC | O_CREAT | _O_NOINHERIT | _O_BINARY
2444 : O_WRONLY | O_APPEND | O_CREAT | _O_NOINHERIT | _O_BINARY,
2445 0666);
2446 if (fd >= 0)
2447 {
2448 ssize_t cbWritten = write(fd, pChild->u.Append.pszAppend, pChild->u.Append.cbAppend);
2449 if (cbWritten == (ssize_t)pChild->u.Append.cbAppend)
2450 {
2451 if (close(fd) >= 0)
2452 {
2453 pChild->iExitCode = 0;
2454 return;
2455 }
2456 MkWinChildError(pWorker, 1, "kmk_builtin_append: close failed on '%s': %u (%s)\n",
2457 pChild->u.Append.pszFilename, errno, strerror(errno));
2458 }
2459 else
2460 MkWinChildError(pWorker, 1, "kmk_builtin_append: error writing %lu bytes to on '%s': %u (%s)\n",
2461 pChild->u.Append.cbAppend, pChild->u.Append.pszFilename, errno, strerror(errno));
2462 close(fd);
2463 }
2464 else
2465 MkWinChildError(pWorker, 1, "kmk_builtin_append: error opening '%s': %u (%s)\n",
2466 pChild->u.Append.pszFilename, errno, strerror(errno));
2467 pChild->iExitCode = 1;
2468}
2469
2470/**
2471 * Childcare worker: handle kSubmit job.
2472 *
2473 * @param pWorker The worker.
2474 * @param pChild The kSubmit child.
2475 */
2476static void mkWinChildcareWorkerThreadHandleSubmit(PWINCHILDCAREWORKER pWorker, PWINCHILD pChild)
2477{
2478 void *pvSubmitWorker = pChild->u.Submit.pvSubmitWorker;
2479
2480 /*
2481 * Prep the wait handles.
2482 */
2483 HANDLE ahHandles[3] = { pChild->u.Submit.hEvent, NULL, NULL };
2484 DWORD cHandles = 1;
2485 if (pChild->u.Submit.pStdOut)
2486 {
2487 assert(pChild->u.Submit.pStdErr);
2488 pChild->u.Submit.pStdOut->fHaveWrittenOut = FALSE;
2489 ahHandles[cHandles++] = pChild->u.Submit.pStdOut->hEvent;
2490 pChild->u.Submit.pStdErr->fHaveWrittenOut = FALSE;
2491 ahHandles[cHandles++] = pChild->u.Submit.pStdErr->hEvent;
2492 }
2493
2494 /*
2495 * Wait loop.
2496 */
2497 for (;;)
2498 {
2499 int iExitCode = -42;
2500 int iSignal = -1;
2501 DWORD dwStatus;
2502 if (cHandles == 1)
2503 dwStatus = WaitForSingleObject(ahHandles[0], INFINITE);
2504 else
2505 {
2506 dwStatus = WaitForMultipleObjects(cHandles, ahHandles, FALSE /*fWaitAll*/, INFINITE);
2507 assert(dwStatus != WAIT_FAILED);
2508 if (dwStatus == WAIT_OBJECT_0 + 1)
2509 mkWinChildcareWorkerCatchOutput(pChild, pChild->u.Submit.pStdOut, FALSE /*fDraining*/);
2510 else if (dwStatus == WAIT_OBJECT_0 + 2)
2511 mkWinChildcareWorkerCatchOutput(pChild, pChild->u.Submit.pStdErr, FALSE /*fDraining*/);
2512 }
2513 if (kSubmitSubProcGetResult((intptr_t)pvSubmitWorker, dwStatus == WAIT_OBJECT_0 /*fBlock*/, &iExitCode, &iSignal) == 0)
2514 {
2515 if (pChild->u.Submit.pStdOut)
2516 MkWinChildcareWorkerDrainPipes(pChild, pChild->u.Submit.pStdOut, pChild->u.Submit.pStdErr);
2517
2518 pChild->iExitCode = iExitCode;
2519 pChild->iSignal = iSignal;
2520 /* Cleanup must be done on the main thread. */
2521 return;
2522 }
2523
2524 if (pChild->iSignal != 0)
2525 kSubmitSubProcKill((intptr_t)pvSubmitWorker, pChild->iSignal);
2526 }
2527}
2528
2529/**
2530 * Childcare worker: handle kmk_redirect process.
2531 *
2532 * @param pWorker The worker.
2533 * @param pChild The redirect child.
2534 */
2535static void mkWinChildcareWorkerThreadHandleRedirect(PWINCHILDCAREWORKER pWorker, PWINCHILD pChild)
2536{
2537 mkWinChildcareWorkerWaitForProcess(pWorker, pChild, pChild->u.Redirect.hProcess, L"kmk_redirect", FALSE /*fCatchOutput*/);
2538}
2539
2540#endif /* KMK */
2541
2542/**
2543 * Childcare worker thread.
2544 *
2545 * @returns 0
2546 * @param pvUser The worker instance.
2547 */
2548static unsigned int __stdcall mkWinChildcareWorkerThread(void *pvUser)
2549{
2550 PWINCHILDCAREWORKER pWorker = (PWINCHILDCAREWORKER)pvUser;
2551 assert(pWorker->uMagic == WINCHILDCAREWORKER_MAGIC);
2552
2553#ifdef MKWINCHILD_DO_SET_PROCESSOR_GROUP
2554 /*
2555 * Adjust process group if necessary.
2556 *
2557 * Note! It seems that setting the mask to zero means that we select all
2558 * active processors. Couldn't find any alternative API for getting
2559 * the correct active processor mask.
2560 */
2561 if (g_cProcessorGroups > 1)
2562 {
2563 GROUP_AFFINITY Affinity = { 0 /* == all active apparently */, pWorker->iProcessorGroup, { 0, 0, 0 } };
2564 BOOL fRet = g_pfnSetThreadGroupAffinity(GetCurrentThread(), &Affinity, NULL);
2565 assert(fRet); (void)fRet;
2566# ifndef NDEBUG
2567 {
2568 GROUP_AFFINITY ActualAffinity = { 0xbeefbeefU, 0xbeef, { 0xbeef, 0xbeef, 0xbeef } };
2569 fRet = GetThreadGroupAffinity(GetCurrentThread(), &ActualAffinity);
2570 assert(fRet); (void)fRet;
2571 assert(ActualAffinity.Group == pWorker->iProcessorGroup);
2572 }
2573# endif
2574 }
2575#endif
2576
2577 /*
2578 * Work loop.
2579 */
2580 while (!g_fShutdown)
2581 {
2582 /*
2583 * Try go idle.
2584 */
2585 PWINCHILD pChild = pWorker->pTailTodoChildren;
2586 if (!pChild)
2587 {
2588 _InterlockedExchange(&pWorker->fIdle, TRUE);
2589 pChild = pWorker->pTailTodoChildren;
2590 if (!pChild)
2591 {
2592 DWORD dwStatus;
2593
2594 _InterlockedIncrement((long *)&g_cIdleChildcareWorkers);
2595 _InterlockedExchange((long *)&g_idxLastChildcareWorker, pWorker->idxWorker);
2596 dwStatus = WaitForSingleObject(pWorker->hEvtIdle, INFINITE);
2597 _InterlockedExchange(&pWorker->fIdle, FALSE);
2598 _InterlockedDecrement((long *)&g_cIdleChildcareWorkers);
2599
2600 assert(dwStatus != WAIT_FAILED);
2601 if (dwStatus == WAIT_FAILED)
2602 Sleep(20);
2603
2604 pChild = pWorker->pTailTodoChildren;
2605 }
2606 else
2607 _InterlockedExchange(&pWorker->fIdle, FALSE);
2608 }
2609 if (pChild)
2610 {
2611 /*
2612 * We got work to do. First job is to deque the job.
2613 */
2614 pChild = mkWinChildDequeFromLifo(&pWorker->pTailTodoChildren, pChild);
2615 assert(pChild);
2616 if (pChild)
2617 {
2618 PWINCHILD pTailExpect;
2619
2620 pChild->pWorker = pWorker;
2621 pWorker->pCurChild = pChild;
2622 switch (pChild->enmType)
2623 {
2624 case WINCHILDTYPE_PROCESS:
2625 mkWinChildcareWorkerThreadHandleProcess(pWorker, pChild);
2626 break;
2627#ifdef KMK
2628 case WINCHILDTYPE_BUILT_IN:
2629 mkWinChildcareWorkerThreadHandleBuiltIn(pWorker, pChild);
2630 break;
2631 case WINCHILDTYPE_APPEND:
2632 mkWinChildcareWorkerThreadHandleAppend(pWorker, pChild);
2633 break;
2634 case WINCHILDTYPE_SUBMIT:
2635 mkWinChildcareWorkerThreadHandleSubmit(pWorker, pChild);
2636 break;
2637 case WINCHILDTYPE_REDIRECT:
2638 mkWinChildcareWorkerThreadHandleRedirect(pWorker, pChild);
2639 break;
2640#endif
2641 default:
2642 assert(0);
2643 }
2644 pWorker->pCurChild = NULL;
2645 pChild->pWorker = NULL;
2646
2647 /*
2648 * Move the child to the completed list.
2649 */
2650 pTailExpect = NULL;
2651 for (;;)
2652 {
2653 PWINCHILD pTailActual;
2654 pChild->pNext = pTailExpect;
2655 pTailActual = _InterlockedCompareExchangePointer(&g_pTailCompletedChildren, pChild, pTailExpect);
2656 if (pTailActual != pTailExpect)
2657 pTailExpect = pTailActual;
2658 else
2659 {
2660 _InterlockedDecrement(&g_cPendingChildren);
2661 if (pTailExpect)
2662 break;
2663 if (SetEvent(g_hEvtWaitChildren))
2664 break;
2665 MkWinChildError(pWorker, 1, "SetEvent(g_hEvtWaitChildren=%p) failed: %u\n",
2666 g_hEvtWaitChildren, GetLastError());
2667 break;
2668 }
2669 }
2670 }
2671 }
2672 }
2673
2674 _endthreadex(0);
2675 return 0;
2676}
2677
2678/**
2679 * Creates a pipe for catching child output.
2680 *
2681 * This is a custom CreatePipe implementation that allows for overlapped I/O on
2682 * our end of the pipe. Silly that they don't offer an API that does this.
2683 *
2684 * @returns The pipe that was created. NULL on failure.
2685 * @param pPipe The structure for the pipe.
2686 * @param iWhich Which standard descriptor this is a pipe for.
2687 * @param idxWorker The worker index.
2688 */
2689PWINCCWPIPE MkWinChildcareCreateWorkerPipe(unsigned iWhich, unsigned int idxWorker)
2690{
2691 /*
2692 * We try generate a reasonably unique name from the get go, so this retry
2693 * loop shouldn't really ever be needed. But you never know.
2694 */
2695 static unsigned s_iSeqNo = 0;
2696 DWORD const cMaxInstances = 1;
2697 DWORD const cbPipe = 4096;
2698 DWORD const cMsTimeout = 0;
2699 unsigned cTries = 256;
2700 while (cTries-- > 0)
2701 {
2702 /* Create the pipe (our end). */
2703 HANDLE hPipeRead;
2704 DWORD fOpenMode = PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED | FILE_FLAG_FIRST_PIPE_INSTANCE;
2705 DWORD fPipeMode = PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT | PIPE_REJECT_REMOTE_CLIENTS;
2706 WCHAR wszName[MAX_PATH];
2707 s_iSeqNo++;
2708 _snwprintf(wszName, MAX_PATH, L"\\\\.\\pipe\\kmk-winchildren-%u-%u-%u-%s-%u-%u",
2709 GetCurrentProcessId(), GetCurrentThreadId(), idxWorker, iWhich == 1 ? L"out" : L"err", s_iSeqNo, GetTickCount());
2710 hPipeRead = CreateNamedPipeW(wszName, fOpenMode, fPipeMode, cMaxInstances, cbPipe, cbPipe, cMsTimeout, NULL /*pSecAttr*/);
2711 if (hPipeRead == INVALID_HANDLE_VALUE && GetLastError() == ERROR_INVALID_PARAMETER)
2712 {
2713 fOpenMode &= ~FILE_FLAG_FIRST_PIPE_INSTANCE;
2714 fPipeMode &= ~PIPE_REJECT_REMOTE_CLIENTS;
2715 hPipeRead = CreateNamedPipeW(wszName, fOpenMode, fPipeMode, cMaxInstances, cbPipe, cbPipe, cMsTimeout, NULL /*pSecAttr*/);
2716 }
2717 if (hPipeRead != INVALID_HANDLE_VALUE)
2718 {
2719 /* Connect the other end. */
2720 HANDLE hPipeWrite = CreateFileW(wszName, GENERIC_WRITE | FILE_READ_ATTRIBUTES, 0 /*fShareMode*/, NULL /*pSecAttr*/,
2721 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL /*hTemplateFile*/);
2722 if (hPipeWrite != INVALID_HANDLE_VALUE)
2723 {
2724 /*
2725 * Create the event object and we're done.
2726 *
2727 * It starts in signalled stated so we don't need special code
2728 * for handing when we start waiting.
2729 */
2730 HANDLE hEvent = CreateEventW(NULL /*pSecAttr*/, TRUE /*fManualReset*/, TRUE /*fInitialState*/, NULL /*pwszName*/);
2731 if (hEvent != NULL)
2732 {
2733 PWINCCWPIPE pPipe = (PWINCCWPIPE)xcalloc(sizeof(*pPipe));
2734 pPipe->hPipeMine = hPipeRead;
2735 pPipe->hPipeChild = hPipeWrite;
2736 pPipe->hEvent = hEvent;
2737 pPipe->iWhich = iWhich;
2738 pPipe->fReadPending = FALSE;
2739 pPipe->cbBuffer = cbPipe;
2740 pPipe->pbBuffer = xcalloc(cbPipe);
2741 return pPipe;
2742 }
2743
2744 CloseHandle(hPipeWrite);
2745 CloseHandle(hPipeRead);
2746 return NULL;
2747 }
2748 CloseHandle(hPipeRead);
2749 }
2750 }
2751 return NULL;
2752}
2753
2754/**
2755 * Destroys a childcare worker pipe.
2756 *
2757 * @param pPipe The pipe.
2758 */
2759void MkWinChildcareDeleteWorkerPipe(PWINCCWPIPE pPipe)
2760{
2761 if (pPipe->hPipeChild != NULL)
2762 {
2763 CloseHandle(pPipe->hPipeChild);
2764 pPipe->hPipeChild = NULL;
2765 }
2766
2767 if (pPipe->hPipeMine != NULL)
2768 {
2769 if (pPipe->fReadPending)
2770 if (!CancelIo(pPipe->hPipeMine))
2771 WaitForSingleObject(pPipe->hEvent, INFINITE);
2772 CloseHandle(pPipe->hPipeMine);
2773 pPipe->hPipeMine = NULL;
2774 }
2775
2776 if (pPipe->hEvent != NULL)
2777 {
2778 CloseHandle(pPipe->hEvent);
2779 pPipe->hEvent = NULL;
2780 }
2781
2782 if (pPipe->pbBuffer)
2783 {
2784 free(pPipe->pbBuffer);
2785 pPipe->pbBuffer = NULL;
2786 }
2787}
2788
2789/**
2790 * Initializes the processor group allocator.
2791 *
2792 * @param pState The allocator to initialize.
2793 */
2794void MkWinChildInitCpuGroupAllocator(PMKWINCHILDCPUGROUPALLOCSTATE pState)
2795{
2796 /* We shift the starting group with the make nesting level as part of
2797 our very simple distribution strategy. */
2798 pState->idxGroup = makelevel;
2799 pState->idxProcessorInGroup = 0;
2800}
2801
2802/**
2803 * Allocate CPU group for the next child process.
2804 *
2805 * @returns CPU group.
2806 * @param pState The allocator state. Must be initialized by
2807 * MkWinChildInitCpuGroupAllocator().
2808 */
2809unsigned int MkWinChildAllocateCpuGroup(PMKWINCHILDCPUGROUPALLOCSTATE pState)
2810{
2811 unsigned int iGroup = 0;
2812 if (g_cProcessorGroups > 1)
2813 {
2814 unsigned int cMaxInGroup;
2815 unsigned int cInGroup;
2816
2817 iGroup = pState->idxGroup % g_cProcessorGroups;
2818
2819 /* Advance. We employ a very simple strategy that does 50% in
2820 each group for each group cycle. Odd processor counts are
2821 caught in odd group cycles. The init function selects the
2822 starting group based on make nesting level to avoid stressing
2823 out the first group. */
2824 cInGroup = ++pState->idxProcessorInGroup;
2825 cMaxInGroup = g_pacProcessorsInGroup[iGroup];
2826 if ( !(cMaxInGroup & 1)
2827 || !((pState->idxGroup / g_cProcessorGroups) & 1))
2828 cMaxInGroup /= 2;
2829 else
2830 cMaxInGroup = cMaxInGroup / 2 + 1;
2831 if (cInGroup >= cMaxInGroup)
2832 {
2833 pState->idxProcessorInGroup = 0;
2834 pState->idxGroup++;
2835 }
2836 }
2837 return iGroup;
2838}
2839
2840/**
2841 * Creates another childcare worker.
2842 *
2843 * @returns The new worker, if we succeeded.
2844 */
2845static PWINCHILDCAREWORKER mkWinChildcareCreateWorker(void)
2846{
2847 PWINCHILDCAREWORKER pWorker = (PWINCHILDCAREWORKER)xcalloc(sizeof(*pWorker));
2848 pWorker->uMagic = WINCHILDCAREWORKER_MAGIC;
2849 pWorker->idxWorker = g_cChildCareworkers;
2850 pWorker->hEvtIdle = CreateEventW(NULL, FALSE /*fManualReset*/, FALSE /*fInitialState*/, NULL /*pwszName*/);
2851 if (pWorker->hEvtIdle)
2852 {
2853 pWorker->pStdOut = MkWinChildcareCreateWorkerPipe(1, pWorker->idxWorker);
2854 if (pWorker->pStdOut)
2855 {
2856 pWorker->pStdErr = MkWinChildcareCreateWorkerPipe(2, pWorker->idxWorker);
2857 if (pWorker->pStdErr)
2858 {
2859 /* Before we start the thread, assign it to a processor group. */
2860 pWorker->iProcessorGroup = MkWinChildAllocateCpuGroup(&g_ProcessorGroupAllocator);
2861
2862 /* Try start the thread. */
2863 pWorker->hThread = (HANDLE)_beginthreadex(NULL, 0 /*cbStack*/, mkWinChildcareWorkerThread, pWorker,
2864 0, &pWorker->tid);
2865 if (pWorker->hThread != NULL)
2866 {
2867 pWorker->idxWorker = g_cChildCareworkers++; /* paranoia */
2868 g_papChildCareworkers[pWorker->idxWorker] = pWorker;
2869 return pWorker;
2870 }
2871
2872 /* Bail out! */
2873 ONS (error, NILF, "_beginthreadex failed: %u (%s)\n", errno, strerror(errno));
2874 MkWinChildcareDeleteWorkerPipe(pWorker->pStdErr);
2875 }
2876 else
2877 ON (error, NILF, "Failed to create stderr pipe: %u\n", GetLastError());
2878 MkWinChildcareDeleteWorkerPipe(pWorker->pStdOut);
2879 }
2880 else
2881 ON (error, NILF, "Failed to create stdout pipe: %u\n", GetLastError());
2882 CloseHandle(pWorker->hEvtIdle);
2883 }
2884 else
2885 ON (error, NILF, "CreateEvent failed: %u\n", GetLastError());
2886 pWorker->uMagic = ~WINCHILDCAREWORKER_MAGIC;
2887 free(pWorker);
2888 return NULL;
2889}
2890
2891/**
2892 * Helper for copying argument and environment vectors.
2893 *
2894 * @returns Single alloc block copy.
2895 * @param papszSrc The source vector.
2896 * @param pcbStrings Where to return the size of the strings & terminator.
2897 */
2898static char **mkWinChildCopyStringArray(char **papszSrc, size_t *pcbStrings)
2899{
2900 const char *psz;
2901 char **papszDstArray;
2902 char *pszDstStr;
2903 size_t i;
2904
2905 /* Calc sizes first. */
2906 size_t cbStrings = 1; /* (one extra for terminator string) */
2907 size_t cStrings = 0;
2908 while ((psz = papszSrc[cStrings]) != NULL)
2909 {
2910 cbStrings += strlen(psz) + 1;
2911 cStrings++;
2912 }
2913 *pcbStrings = cbStrings;
2914
2915 /* Allocate destination. */
2916 papszDstArray = (char **)xmalloc(cbStrings + (cStrings + 1) * sizeof(papszDstArray[0]));
2917 pszDstStr = (char *)&papszDstArray[cStrings + 1];
2918
2919 /* Copy it. */
2920 for (i = 0; i < cStrings; i++)
2921 {
2922 const char *pszSource = papszSrc[i];
2923 size_t cchString = strlen(pszSource);
2924 papszDstArray[i] = pszDstStr;
2925 memcpy(pszDstStr, pszSource, cchString);
2926 pszDstStr += cchString;
2927 *pszDstStr++ = '\0';
2928 }
2929 *pszDstStr = '\0';
2930 assert(&pszDstStr[1] - papszDstArray[0] == cbStrings);
2931 papszDstArray[i] = NULL;
2932 return papszDstArray;
2933}
2934
2935/**
2936 * Allocate and init a WINCHILD.
2937 *
2938 * @returns The new windows child structure.
2939 * @param enmType The child type.
2940 */
2941static PWINCHILD mkWinChildNew(WINCHILDTYPE enmType)
2942{
2943 PWINCHILD pChild = xcalloc(sizeof(*pChild));
2944 pChild->enmType = enmType;
2945 pChild->fCoreDumped = 0;
2946 pChild->iSignal = 0;
2947 pChild->iExitCode = 222222;
2948 pChild->uMagic = WINCHILD_MAGIC;
2949 pChild->pid = (intptr_t)pChild;
2950 return pChild;
2951}
2952
2953/**
2954 * Destructor for WINCHILD.
2955 *
2956 * @param pChild The child structure to destroy.
2957 */
2958static void mkWinChildDelete(PWINCHILD pChild)
2959{
2960 assert(pChild->uMagic == WINCHILD_MAGIC);
2961 pChild->uMagic = ~WINCHILD_MAGIC;
2962
2963 switch (pChild->enmType)
2964 {
2965 case WINCHILDTYPE_PROCESS:
2966 {
2967 if (pChild->u.Process.papszArgs)
2968 {
2969 free(pChild->u.Process.papszArgs);
2970 pChild->u.Process.papszArgs = NULL;
2971 }
2972 if (pChild->u.Process.cbEnvStrings && pChild->u.Process.papszEnv)
2973 {
2974 free(pChild->u.Process.papszEnv);
2975 pChild->u.Process.papszEnv = NULL;
2976 }
2977 if (pChild->u.Process.pszShell)
2978 {
2979 free(pChild->u.Process.pszShell);
2980 pChild->u.Process.pszShell = NULL;
2981 }
2982 if (pChild->u.Process.hProcess)
2983 {
2984 CloseHandle(pChild->u.Process.hProcess);
2985 pChild->u.Process.hProcess = NULL;
2986 }
2987 mkWinChildcareWorkerCloseStandardHandles(pChild);
2988 break;
2989 }
2990
2991#ifdef KMK
2992 case WINCHILDTYPE_BUILT_IN:
2993 if (pChild->u.BuiltIn.papszArgs)
2994 {
2995 free(pChild->u.BuiltIn.papszArgs);
2996 pChild->u.BuiltIn.papszArgs = NULL;
2997 }
2998 if (pChild->u.BuiltIn.papszEnv)
2999 {
3000 free(pChild->u.BuiltIn.papszEnv);
3001 pChild->u.BuiltIn.papszEnv = NULL;
3002 }
3003 break;
3004
3005 case WINCHILDTYPE_APPEND:
3006 if (pChild->u.Append.pszFilename)
3007 {
3008 free(pChild->u.Append.pszFilename);
3009 pChild->u.Append.pszFilename = NULL;
3010 }
3011 if (pChild->u.Append.pszAppend)
3012 {
3013 free(pChild->u.Append.pszAppend);
3014 pChild->u.Append.pszAppend = NULL;
3015 }
3016 break;
3017
3018 case WINCHILDTYPE_SUBMIT:
3019 if (pChild->u.Submit.pvSubmitWorker)
3020 {
3021 kSubmitSubProcCleanup((intptr_t)pChild->u.Submit.pvSubmitWorker);
3022 pChild->u.Submit.pvSubmitWorker = NULL;
3023 }
3024 break;
3025
3026 case WINCHILDTYPE_REDIRECT:
3027 if (pChild->u.Redirect.hProcess)
3028 {
3029 CloseHandle(pChild->u.Redirect.hProcess);
3030 pChild->u.Redirect.hProcess = NULL;
3031 }
3032 break;
3033#endif /* KMK */
3034
3035 default:
3036 assert(0);
3037 }
3038
3039 free(pChild);
3040}
3041
3042/**
3043 * Queues the child with a worker, creating new workers if necessary.
3044 *
3045 * @returns 0 on success, windows error code on failure (child destroyed).
3046 * @param pChild The child.
3047 * @param pPid Where to return the PID (optional).
3048 */
3049static int mkWinChildPushToCareWorker(PWINCHILD pChild, pid_t *pPid)
3050{
3051 PWINCHILDCAREWORKER pWorker = NULL;
3052 PWINCHILD pOldChild;
3053 PWINCHILD pCurChild;
3054
3055 /*
3056 * There are usually idle workers around, except for at the start.
3057 */
3058 if (g_cIdleChildcareWorkers > 0)
3059 {
3060 /*
3061 * Try the idle hint first and move forward from it.
3062 */
3063 unsigned int const cWorkers = g_cChildCareworkers;
3064 unsigned int iHint = g_idxLastChildcareWorker;
3065 unsigned int i;
3066 for (i = iHint; i < cWorkers; i++)
3067 {
3068 PWINCHILDCAREWORKER pPossibleWorker = g_papChildCareworkers[i];
3069 if (pPossibleWorker->fIdle)
3070 {
3071 pWorker = pPossibleWorker;
3072 break;
3073 }
3074 }
3075 if (!pWorker)
3076 {
3077 /* Scan from the start. */
3078 if (iHint > cWorkers)
3079 iHint = cWorkers;
3080 for (i = 0; i < iHint; i++)
3081 {
3082 PWINCHILDCAREWORKER pPossibleWorker = g_papChildCareworkers[i];
3083 if (pPossibleWorker->fIdle)
3084 {
3085 pWorker = pPossibleWorker;
3086 break;
3087 }
3088 }
3089 }
3090 }
3091 if (!pWorker)
3092 {
3093 /*
3094 * Try create more workers if we haven't reached the max yet.
3095 */
3096 if (g_cChildCareworkers < g_cChildCareworkersMax)
3097 pWorker = mkWinChildcareCreateWorker();
3098
3099 /*
3100 * Queue it with an existing worker. Look for one without anthing extra scheduled.
3101 */
3102 if (!pWorker)
3103 {
3104 unsigned int i = g_cChildCareworkers;
3105 if (i == 0)
3106 fatal(NILF, 0, _("Failed to create worker threads for managing child processes!\n"));
3107 pWorker = g_papChildCareworkers[--i];
3108 if (pWorker->pTailTodoChildren)
3109 while (i-- > 0)
3110 {
3111 PWINCHILDCAREWORKER pPossibleWorker = g_papChildCareworkers[i];
3112 if (!pPossibleWorker->pTailTodoChildren)
3113 {
3114 pWorker = pPossibleWorker;
3115 break;
3116 }
3117 }
3118 }
3119 }
3120
3121 /*
3122 * Do the queueing.
3123 */
3124 pOldChild = NULL;
3125 for (;;)
3126 {
3127 pChild->pNext = pOldChild;
3128 pCurChild = _InterlockedCompareExchangePointer((void **)&pWorker->pTailTodoChildren, pChild, pOldChild);
3129 if (pCurChild == pOldChild)
3130 {
3131 DWORD volatile dwErr;
3132 _InterlockedIncrement(&g_cPendingChildren);
3133 if ( !pWorker->fIdle
3134 || SetEvent(pWorker->hEvtIdle))
3135 {
3136 *pPid = pChild->pid;
3137 return 0;
3138 }
3139
3140 _InterlockedDecrement(&g_cPendingChildren);
3141 dwErr = GetLastError();
3142 assert(0);
3143 mkWinChildDelete(pChild);
3144 return dwErr ? dwErr : -20;
3145 }
3146 pOldChild = pCurChild;
3147 }
3148}
3149
3150/**
3151 * Creates a regular child process (job.c).
3152 *
3153 * Will copy the information and push it to a childcare thread that does the
3154 * actual process creation.
3155 *
3156 * @returns 0 on success, windows status code on failure.
3157 * @param papszArgs The arguments.
3158 * @param papszEnv The environment (optional).
3159 * @param pszShell The SHELL variable value (optional).
3160 * @param pMkChild The make child structure (optional).
3161 * @param pPid Where to return the pid.
3162 */
3163int MkWinChildCreate(char **papszArgs, char **papszEnv, const char *pszShell, struct child *pMkChild, pid_t *pPid)
3164{
3165 PWINCHILD pChild = mkWinChildNew(WINCHILDTYPE_PROCESS);
3166 pChild->pMkChild = pMkChild;
3167
3168 pChild->u.Process.papszArgs = mkWinChildCopyStringArray(papszArgs, &pChild->u.Process.cbArgsStrings);
3169 if ( !papszEnv
3170 || !pMkChild
3171 || pMkChild->environment == papszEnv)
3172 {
3173 pChild->u.Process.cbEnvStrings = 0;
3174 pChild->u.Process.papszEnv = papszEnv;
3175 }
3176 else
3177 pChild->u.Process.papszEnv = mkWinChildCopyStringArray(papszEnv, &pChild->u.Process.cbEnvStrings);
3178 if (pszShell)
3179 pChild->u.Process.pszShell = xstrdup(pszShell);
3180 pChild->u.Process.hStdOut = INVALID_HANDLE_VALUE;
3181 pChild->u.Process.hStdErr = INVALID_HANDLE_VALUE;
3182
3183 /* We always catch the output in order to prevent character soups courtesy
3184 of the microsoft CRT and/or linkers writing character by character to the
3185 console. Always try write whole lines, even when --output-sync is none. */
3186 pChild->u.Process.fCatchOutput = TRUE;
3187
3188 return mkWinChildPushToCareWorker(pChild, pPid);
3189}
3190
3191/**
3192 * Creates a chile process with a pipe hooked up to stdout.
3193 *
3194 * @returns 0 on success, non-zero on failure.
3195 * @param papszArgs The argument vector.
3196 * @param papszEnv The environment vector (optional).
3197 * @param fdErr File descriptor to hook up to stderr.
3198 * @param pPid Where to return the pid.
3199 * @param pfdReadPipe Where to return the read end of the pipe.
3200 */
3201int MkWinChildCreateWithStdOutPipe(char **papszArgs, char **papszEnv, int fdErr, pid_t *pPid, int *pfdReadPipe)
3202{
3203 /*
3204 * Create the pipe.
3205 */
3206 HANDLE hReadPipe;
3207 HANDLE hWritePipe;
3208 if (CreatePipe(&hReadPipe, &hWritePipe, NULL, 0 /* default size */))
3209 {
3210 //if (SetHandleInformation(hWritePipe, HANDLE_FLAG_INHERIT /* clear */ , HANDLE_FLAG_INHERIT /*set*/))
3211 {
3212 int fdReadPipe = _open_osfhandle((intptr_t)hReadPipe, O_RDONLY);
3213 if (fdReadPipe >= 0)
3214 {
3215 PWINCHILD pChild;
3216 int rc;
3217
3218 /*
3219 * Get a handle for fdErr. Ignore failure.
3220 */
3221 HANDLE hStdErr = INVALID_HANDLE_VALUE;
3222 if (fdErr >= 0)
3223 {
3224 HANDLE hNative = (HANDLE)_get_osfhandle(fdErr);
3225 if (!DuplicateHandle(GetCurrentProcess(), hNative, GetCurrentProcess(),
3226 &hStdErr, 0 /*DesiredAccess*/, TRUE /*fInherit*/, DUPLICATE_SAME_ACCESS))
3227 {
3228 ONN(error, NILF, _("DuplicateHandle failed on stderr descriptor (%u): %u\n"), fdErr, GetLastError());
3229 hStdErr = INVALID_HANDLE_VALUE;
3230 }
3231 }
3232
3233 /*
3234 * Push it off to the worker thread.
3235 */
3236 pChild = mkWinChildNew(WINCHILDTYPE_PROCESS);
3237 pChild->u.Process.papszArgs = mkWinChildCopyStringArray(papszArgs, &pChild->u.Process.cbArgsStrings);
3238 pChild->u.Process.papszEnv = mkWinChildCopyStringArray(papszEnv ? papszEnv : environ,
3239 &pChild->u.Process.cbEnvStrings);
3240 //if (pszShell)
3241 // pChild->u.Process.pszShell = xstrdup(pszShell);
3242 pChild->u.Process.hStdOut = hWritePipe;
3243 pChild->u.Process.hStdErr = hStdErr;
3244 pChild->u.Process.fCloseStdErr = TRUE;
3245 pChild->u.Process.fCloseStdOut = TRUE;
3246
3247 rc = mkWinChildPushToCareWorker(pChild, pPid);
3248 if (rc == 0)
3249 *pfdReadPipe = fdReadPipe;
3250 else
3251 {
3252 ON(error, NILF, _("mkWinChildPushToCareWorker failed on pipe: %d\n"), rc);
3253 close(fdReadPipe);
3254 *pfdReadPipe = -1;
3255 *pPid = -1;
3256 }
3257 return rc;
3258 }
3259
3260 ON(error, NILF, _("_open_osfhandle failed on pipe: %u\n"), errno);
3261 }
3262 //else
3263 // ON(error, NILF, _("SetHandleInformation failed on pipe: %u\n"), GetLastError());
3264 if (hReadPipe != INVALID_HANDLE_VALUE)
3265 CloseHandle(hReadPipe);
3266 CloseHandle(hWritePipe);
3267 }
3268 else
3269 ON(error, NILF, _("CreatePipe failed: %u\n"), GetLastError());
3270 *pfdReadPipe = -1;
3271 *pPid = -1;
3272 return -1;
3273}
3274
3275#ifdef KMK
3276
3277/**
3278 * Interface used by kmkbuiltin.c for executing builtin commands on threads.
3279 *
3280 * @returns 0 on success, windows status code on failure.
3281 * @param pBuiltIn The kmk built-in command entry.
3282 * @param cArgs The number of arguments in papszArgs.
3283 * @param papszArgs The argument vector.
3284 * @param papszEnv The environment vector, optional.
3285 * @param pMkChild The make child structure.
3286 * @param pPid Where to return the pid.
3287 */
3288int MkWinChildCreateBuiltIn(PCKMKBUILTINENTRY pBuiltIn, int cArgs, char **papszArgs, char **papszEnv,
3289 struct child *pMkChild, pid_t *pPid)
3290{
3291 size_t cbIgnored;
3292 PWINCHILD pChild = mkWinChildNew(WINCHILDTYPE_BUILT_IN);
3293 pChild->pMkChild = pMkChild;
3294 pChild->u.BuiltIn.pBuiltIn = pBuiltIn;
3295 pChild->u.BuiltIn.cArgs = cArgs;
3296 pChild->u.BuiltIn.papszArgs = mkWinChildCopyStringArray(papszArgs, &cbIgnored);
3297 pChild->u.BuiltIn.papszEnv = papszEnv ? mkWinChildCopyStringArray(papszEnv, &cbIgnored) : NULL;
3298 return mkWinChildPushToCareWorker(pChild, pPid);
3299}
3300
3301/**
3302 * Interface used by append.c for do the slow file system part.
3303 *
3304 * This will append the given buffer to the specified file and free the buffer.
3305 *
3306 * @returns 0 on success, windows status code on failure.
3307 *
3308 * @param pszFilename The name of the file to append to.
3309 * @param ppszAppend What to append. The pointer pointed to is set to
3310 * NULL once we've taken ownership of the buffer and
3311 * promise to free it.
3312 * @param cbAppend How much to append.
3313 * @param fTruncate Whether to truncate the file before appending to it.
3314 * @param pMkChild The make child structure.
3315 * @param pPid Where to return the pid.
3316 */
3317int MkWinChildCreateAppend(const char *pszFilename, char **ppszAppend, size_t cbAppend, int fTruncate,
3318 struct child *pMkChild, pid_t *pPid)
3319{
3320 size_t cbFilename = strlen(pszFilename) + 1;
3321 PWINCHILD pChild = mkWinChildNew(WINCHILDTYPE_APPEND);
3322 pChild->pMkChild = pMkChild;
3323 pChild->u.Append.fTruncate = fTruncate;
3324 pChild->u.Append.pszAppend = *ppszAppend;
3325 pChild->u.Append.cbAppend = cbAppend;
3326 pChild->u.Append.pszFilename = (char *)memcpy(xmalloc(cbFilename), pszFilename, cbFilename);
3327 *ppszAppend = NULL;
3328 return mkWinChildPushToCareWorker(pChild, pPid);
3329}
3330
3331/**
3332 * Interface used by kSubmit.c for registering stuff to wait on.
3333 *
3334 * @returns 0 on success, windows status code on failure.
3335 * @param hEvent The event object handle to wait on.
3336 * @param pvSubmitWorker The argument to pass back to kSubmit to clean up.
3337 * @param pStdOut Standard output pipe for the worker. Optional.
3338 * @param pStdErr Standard error pipe for the worker. Optional.
3339 * @param pMkChild The make child structure.
3340 * @param pPid Where to return the pid.
3341 */
3342int MkWinChildCreateSubmit(intptr_t hEvent, void *pvSubmitWorker, PWINCCWPIPE pStdOut, PWINCCWPIPE pStdErr,
3343 struct child *pMkChild, pid_t *pPid)
3344{
3345 PWINCHILD pChild = mkWinChildNew(WINCHILDTYPE_SUBMIT);
3346 pChild->pMkChild = pMkChild;
3347 pChild->u.Submit.hEvent = (HANDLE)hEvent;
3348 pChild->u.Submit.pvSubmitWorker = pvSubmitWorker;
3349 pChild->u.Submit.pStdOut = pStdOut;
3350 pChild->u.Submit.pStdErr = pStdErr;
3351 return mkWinChildPushToCareWorker(pChild, pPid);
3352}
3353
3354/**
3355 * Interface used by redirect.c for registering stuff to wait on.
3356 *
3357 * @returns 0 on success, windows status code on failure.
3358 * @param hProcess The process object to wait on.
3359 * @param pPid Where to return the pid.
3360 */
3361int MkWinChildCreateRedirect(intptr_t hProcess, pid_t *pPid)
3362{
3363 PWINCHILD pChild = mkWinChildNew(WINCHILDTYPE_REDIRECT);
3364 pChild->u.Redirect.hProcess = (HANDLE)hProcess;
3365 return mkWinChildPushToCareWorker(pChild, pPid);
3366}
3367
3368
3369/**
3370 * New interface used by redirect.c for spawning and waitin on a child.
3371 *
3372 * This interface is only used when kmk_builtin_redirect is already running on
3373 * a worker thread.
3374 *
3375 * @returns exit status.
3376 * @param pvWorker The worker instance.
3377 * @param pszExecutable The executable image to run.
3378 * @param papszArgs Argument vector.
3379 * @param fQuotedArgv Whether the argument vector is already quoted and
3380 * just need some space to be turned into a command
3381 * line.
3382 * @param papszEnvVars Environment vector.
3383 * @param pszCwd The working directory of the child. Optional.
3384 * @param pafReplace Which standard handles to replace. Maybe modified!
3385 * @param pahReplace The replacement handles. Maybe modified!
3386 *
3387 */
3388int MkWinChildBuiltInExecChild(void *pvWorker, const char *pszExecutable, char **papszArgs, BOOL fQuotedArgv,
3389 char **papszEnvVars, const char *pszCwd, BOOL pafReplace[3], HANDLE pahReplace[3])
3390{
3391 PWINCHILDCAREWORKER pWorker = (PWINCHILDCAREWORKER)pvWorker;
3392 WCHAR *pwszSearchPath = NULL;
3393 WCHAR *pwszzEnvironment = NULL;
3394 WCHAR *pwszCommandLine = NULL;
3395 WCHAR *pwszImageName = NULL;
3396 WCHAR *pwszCwd = NULL;
3397 BOOL fNeedShell = FALSE;
3398 PWINCHILD pChild;
3399 int rc;
3400 assert(pWorker->uMagic == WINCHILDCAREWORKER_MAGIC);
3401 pChild = pWorker->pCurChild;
3402 assert(pChild != NULL && pChild->uMagic == WINCHILD_MAGIC);
3403
3404 /*
3405 * Convert the CWD first since it's optional and we don't need to clean
3406 * up anything here if it fails.
3407 */
3408 if (pszCwd)
3409 {
3410 size_t cchCwd = strlen(pszCwd);
3411 int cwcCwd = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszCwd, cchCwd + 1, NULL, 0);
3412 pwszCwd = xmalloc((cwcCwd + 1) * sizeof(WCHAR)); /* (+1 in case cwcCwd is 0) */
3413 cwcCwd = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszCwd, cchCwd + 1, pwszCwd, cwcCwd + 1);
3414 if (!cwcCwd)
3415 {
3416 rc = GetLastError();
3417 MkWinChildError(pWorker, 1, _("MultiByteToWideChar failed to convert CWD (%s): %u\n"), pszCwd, (unsigned)rc);
3418 return rc;
3419 }
3420 }
3421
3422 /*
3423 * Before we search for the image, we convert the environment so we don't
3424 * have to traverse it twice to find the PATH.
3425 */
3426 rc = mkWinChildcareWorkerConvertEnvironment(pWorker, papszEnvVars ? papszEnvVars : environ, 0/*cbEnvStrings*/,
3427 &pwszzEnvironment, &pwszSearchPath);
3428 /*
3429 * Find the executable and maybe checking if it's a shell script, then
3430 * convert it to a command line.
3431 */
3432 if (rc == 0)
3433 rc = mkWinChildcareWorkerFindImage(pWorker, pszExecutable, pwszSearchPath, pwszzEnvironment, NULL /*pszShell*/,
3434 &pwszImageName, &fNeedShell, &pChild->fProbableClExe);
3435 if (rc == 0)
3436 {
3437 assert(!fNeedShell);
3438 if (!fQuotedArgv)
3439 rc = mkWinChildcareWorkerConvertCommandline(pWorker, papszArgs, 0 /*fFlags*/, &pwszCommandLine);
3440 else
3441 rc = mkWinChildcareWorkerConvertQuotedArgvToCommandline(pWorker, papszArgs, &pwszCommandLine);
3442
3443 /*
3444 * Create the child process.
3445 */
3446 if (rc == 0)
3447 {
3448 HANDLE hProcess;
3449 rc = mkWinChildcareWorkerCreateProcess(pWorker, pwszImageName, pwszCommandLine, pwszzEnvironment,
3450 pwszCwd, pafReplace, pahReplace, TRUE /*fCatchOutput*/, &hProcess);
3451 if (rc == 0)
3452 {
3453 /*
3454 * Wait for the child to complete.
3455 */
3456 rc = mkWinChildcareWorkerWaitForProcess(pWorker, pChild, hProcess, pwszImageName, TRUE /*fCatchOutput*/);
3457 CloseHandle(hProcess);
3458 }
3459 }
3460 }
3461
3462 free(pwszCwd);
3463 free(pwszCommandLine);
3464 free(pwszImageName);
3465 free(pwszzEnvironment);
3466
3467 return rc;
3468}
3469
3470#endif /* CONFIG_NEW_WIN_CHILDREN */
3471
3472/**
3473 * Interface used to kill process when processing Ctrl-C and fatal errors.
3474 *
3475 * @returns 0 on success, -1 & errno on error.
3476 * @param pid The process to kill (PWINCHILD).
3477 * @param iSignal What to kill it with.
3478 * @param pMkChild The make child structure for validation.
3479 */
3480int MkWinChildKill(pid_t pid, int iSignal, struct child *pMkChild)
3481{
3482 PWINCHILD pChild = (PWINCHILD)pid;
3483 if (pChild)
3484 {
3485 assert(pChild->uMagic == WINCHILD_MAGIC);
3486 if (pChild->uMagic == WINCHILD_MAGIC)
3487 {
3488 switch (pChild->enmType)
3489 {
3490 case WINCHILDTYPE_PROCESS:
3491 assert(pChild->pMkChild == pMkChild);
3492 TerminateProcess(pChild->u.Process.hProcess, DBG_TERMINATE_PROCESS);
3493 pChild->iSignal = iSignal;
3494 break;
3495#ifdef KMK
3496 case WINCHILDTYPE_SUBMIT:
3497 {
3498 pChild->iSignal = iSignal;
3499 SetEvent(pChild->u.Submit.hEvent);
3500 break;
3501 }
3502
3503 case WINCHILDTYPE_REDIRECT:
3504 TerminateProcess(pChild->u.Redirect.hProcess, DBG_TERMINATE_PROCESS);
3505 pChild->iSignal = iSignal;
3506 break;
3507
3508 case WINCHILDTYPE_BUILT_IN:
3509 break;
3510
3511#endif /* KMK */
3512 default:
3513 assert(0);
3514 }
3515 }
3516 }
3517 return -1;
3518}
3519
3520/**
3521 * Wait for a child process to complete
3522 *
3523 * @returns 0 on success, windows error code on failure.
3524 * @param fBlock Whether to block.
3525 * @param pPid Where to return the pid if a child process
3526 * completed. This is set to zero if none.
3527 * @param piExitCode Where to return the exit code.
3528 * @param piSignal Where to return the exit signal number.
3529 * @param pfCoreDumped Where to return the core dumped indicator.
3530 * @param ppMkChild Where to return the associated struct child pointer.
3531 */
3532int MkWinChildWait(int fBlock, pid_t *pPid, int *piExitCode, int *piSignal, int *pfCoreDumped, struct child **ppMkChild)
3533{
3534 PWINCHILD pChild;
3535
3536 *pPid = 0;
3537 *piExitCode = -222222;
3538 *pfCoreDumped = 0;
3539 *ppMkChild = NULL;
3540
3541 /*
3542 * Wait if necessary.
3543 */
3544 if (fBlock && !g_pTailCompletedChildren && g_cPendingChildren > 0)
3545 {
3546 DWORD dwStatus = WaitForSingleObject(g_hEvtWaitChildren, INFINITE);
3547 if (dwStatus == WAIT_FAILED)
3548 return (int)GetLastError();
3549 }
3550
3551 /*
3552 * Try unlink the last child in the LIFO.
3553 */
3554 pChild = g_pTailCompletedChildren;
3555 if (!pChild)
3556 return 0;
3557 pChild = mkWinChildDequeFromLifo(&g_pTailCompletedChildren, pChild);
3558 assert(pChild);
3559
3560 /*
3561 * Set return values and ditch the child structure.
3562 */
3563 *pPid = pChild->pid;
3564 *piExitCode = pChild->iExitCode;
3565 *pfCoreDumped = pChild->fCoreDumped;
3566 *ppMkChild = pChild->pMkChild;
3567 switch (pChild->enmType)
3568 {
3569 case WINCHILDTYPE_PROCESS:
3570 break;
3571#ifdef KMK
3572 case WINCHILDTYPE_BUILT_IN:
3573 case WINCHILDTYPE_APPEND:
3574 case WINCHILDTYPE_SUBMIT:
3575 case WINCHILDTYPE_REDIRECT:
3576 break;
3577#endif /* KMK */
3578 default:
3579 assert(0);
3580 }
3581 mkWinChildDelete(pChild);
3582
3583#ifdef KMK
3584 /* Flush the volatile directory cache. */
3585 dir_cache_invalid_after_job();
3586#endif
3587 return 0;
3588}
3589
3590/**
3591 * Get the child completed event handle.
3592 *
3593 * Needed when w32os.c is waiting for a job token to become available, given
3594 * that completed children is the typical source of these tokens (esp. for kmk).
3595 *
3596 * @returns Zero if no active children, event handle if waiting is required.
3597 */
3598intptr_t MkWinChildGetCompleteEventHandle(void)
3599{
3600 /* We don't return the handle if we've got completed children. This
3601 is a safe guard against being called twice in a row without any
3602 MkWinChildWait call inbetween. */
3603 if (!g_pTailCompletedChildren)
3604 return (intptr_t)g_hEvtWaitChildren;
3605 return 0;
3606}
3607
3608/**
3609 * Emulate execv() for restarting kmk after one or more makefiles has been made.
3610 *
3611 * Does not return.
3612 *
3613 * @param papszArgs The arguments.
3614 * @param papszEnv The environment.
3615 */
3616void MkWinChildReExecMake(char **papszArgs, char **papszEnv)
3617{
3618 PROCESS_INFORMATION ProcInfo;
3619 STARTUPINFOW StartupInfo;
3620 WCHAR *pwszCommandLine;
3621 WCHAR *pwszzEnvironment;
3622 WCHAR *pwszPathIgnored;
3623 int rc;
3624
3625 /*
3626 * Get the executable name.
3627 */
3628 WCHAR wszImageName[MKWINCHILD_MAX_PATH];
3629 DWORD cwcImageName = GetModuleFileNameW(GetModuleHandle(NULL), wszImageName, MKWINCHILD_MAX_PATH);
3630 if (cwcImageName == 0)
3631 ON(fatal, NILF, _("MkWinChildReExecMake: GetModuleFileName failed: %u\n"), GetLastError());
3632
3633 /*
3634 * Create the command line and environment.
3635 */
3636 rc = mkWinChildcareWorkerConvertCommandline(NULL, papszArgs, 0 /*fFlags*/, &pwszCommandLine);
3637 if (rc != 0)
3638 ON(fatal, NILF, _("MkWinChildReExecMake: mkWinChildcareWorkerConvertCommandline failed: %u\n"), rc);
3639
3640 rc = mkWinChildcareWorkerConvertEnvironment(NULL, papszEnv ? papszEnv : environ, 0 /*cbEnvStrings*/,
3641 &pwszzEnvironment, &pwszPathIgnored);
3642 if (rc != 0)
3643 ON(fatal, NILF, _("MkWinChildReExecMake: mkWinChildcareWorkerConvertEnvironment failed: %u\n"), rc);
3644
3645#ifdef KMK
3646 /*
3647 * Flush the file system cache to avoid messing up tools fetching
3648 * going on in the "exec'ed" make by keeping directories open.
3649 */
3650 dir_cache_invalid_all_and_close_dirs(1);
3651#endif
3652
3653 /*
3654 * Fill out the startup info and try create the process.
3655 */
3656 memset(&ProcInfo, 0, sizeof(ProcInfo));
3657 memset(&StartupInfo, 0, sizeof(StartupInfo));
3658 StartupInfo.cb = sizeof(StartupInfo);
3659 GetStartupInfoW(&StartupInfo);
3660 if (!CreateProcessW(wszImageName, pwszCommandLine, NULL /*pProcSecAttr*/, NULL /*pThreadSecAttr*/,
3661 TRUE /*fInheritHandles*/, CREATE_UNICODE_ENVIRONMENT, pwszzEnvironment, NULL /*pwsz*/,
3662 &StartupInfo, &ProcInfo))
3663 ON(fatal, NILF, _("MkWinChildReExecMake: CreateProcessW failed: %u\n"), GetLastError());
3664 CloseHandle(ProcInfo.hThread);
3665
3666 /*
3667 * Wait for it to complete and forward the status code to our parent.
3668 */
3669 for (;;)
3670 {
3671 DWORD dwExitCode = -2222;
3672 DWORD dwStatus = WaitForSingleObject(ProcInfo.hProcess, INFINITE);
3673 if ( dwStatus == WAIT_IO_COMPLETION
3674 || dwStatus == WAIT_TIMEOUT /* whatever */)
3675 continue; /* however unlikely, these aren't fatal. */
3676
3677 /* Get the status code and terminate. */
3678 if (dwStatus == WAIT_OBJECT_0)
3679 {
3680 if (!GetExitCodeProcess(ProcInfo.hProcess, &dwExitCode))
3681 {
3682 ON(fatal, NILF, _("MkWinChildReExecMake: GetExitCodeProcess failed: %u\n"), GetLastError());
3683 dwExitCode = -2222;
3684 }
3685 }
3686 else if (dwStatus)
3687 dwExitCode = dwStatus;
3688
3689 CloseHandle(ProcInfo.hProcess);
3690 for (;;)
3691 exit(dwExitCode);
3692 }
3693}
3694
3695#ifdef WITH_RW_LOCK
3696/** Serialization with kmkbuiltin_redirect. */
3697void MkWinChildExclusiveAcquire(void)
3698{
3699 AcquireSRWLockExclusive(&g_RWLock);
3700}
3701
3702/** Serialization with kmkbuiltin_redirect. */
3703void MkWinChildExclusiveRelease(void)
3704{
3705 ReleaseSRWLockExclusive(&g_RWLock);
3706}
3707#endif /* WITH_RW_LOCK */
3708
3709/**
3710 * Implementation of the CLOSE_ON_EXEC macro.
3711 *
3712 * @returns errno value.
3713 * @param fd The file descriptor to hide from children.
3714 */
3715int MkWinChildUnrelatedCloseOnExec(int fd)
3716{
3717 if (fd >= 0)
3718 {
3719 HANDLE hNative = (HANDLE)_get_osfhandle(fd);
3720 if (hNative != INVALID_HANDLE_VALUE && hNative != NULL)
3721 {
3722 if (SetHandleInformation(hNative, HANDLE_FLAG_INHERIT /*clear*/ , 0 /*set*/))
3723 return 0;
3724 }
3725 return errno;
3726 }
3727 return EINVAL;
3728}
3729
Note: See TracBrowser for help on using the repository browser.