VirtualBox

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

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

winchildren: fixed typo

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

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