diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:21:29 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:21:29 +0000 |
commit | 29cd838eab01ed7110f3ccb2e8c6a35c8a31dbcc (patch) | |
tree | 63ef546b10a81d461e5cf5ed9e98a68cd7dee1aa /src/kmk/kmkbuiltin/kSubmit.c | |
parent | Initial commit. (diff) | |
download | kbuild-29cd838eab01ed7110f3ccb2e8c6a35c8a31dbcc.tar.xz kbuild-29cd838eab01ed7110f3ccb2e8c6a35c8a31dbcc.zip |
Adding upstream version 1:0.1.9998svn3589+dfsg.upstream/1%0.1.9998svn3589+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/kmk/kmkbuiltin/kSubmit.c')
-rw-r--r-- | src/kmk/kmkbuiltin/kSubmit.c | 2116 |
1 files changed, 2116 insertions, 0 deletions
diff --git a/src/kmk/kmkbuiltin/kSubmit.c b/src/kmk/kmkbuiltin/kSubmit.c new file mode 100644 index 0000000..dffa198 --- /dev/null +++ b/src/kmk/kmkbuiltin/kSubmit.c @@ -0,0 +1,2116 @@ +/* $Id: kSubmit.c 3413 2020-08-20 08:20:15Z bird $ */ +/** @file + * kMk Builtin command - submit job to a kWorker. + */ + +/* + * Copyright (c) 2007-2016 knut st. osmundsen <bird-kBuild-spamx@anduin.net> + * + * This file is part of kBuild. + * + * kBuild is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * kBuild is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with kBuild. If not, see <http://www.gnu.org/licenses/> + * + */ + +/******************************************************************************* +* Header Files * +*******************************************************************************/ +#ifdef __APPLE__ +# define _POSIX_C_SOURCE 1 /* 10.4 sdk and unsetenv */ +#endif +#include "makeint.h" +#include "job.h" +#include "variable.h" +#include "pathstuff.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <assert.h> +#ifdef HAVE_ALLOCA_H +# include <alloca.h> +#endif +#if defined(_MSC_VER) +# include <ctype.h> +# include <io.h> +# include <direct.h> +# include <process.h> +#else +# include <unistd.h> +#endif +#ifdef KBUILD_OS_WINDOWS +# ifndef CONFIG_NEW_WIN_CHILDREN +# include "sub_proc.h" +# else +# include "../w32/winchildren.h" +# endif +# include "nt/nt_child_inject_standard_handles.h" +#endif + +#include "kbuild.h" +#include "kmkbuiltin.h" +#include "err.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** Hashes a pid. */ +#define KWORKER_PID_HASH(a_pid) ((size_t)(a_pid) % 61) + +#define TUPLE(a_sz) a_sz, sizeof(a_sz) - 1 + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef struct WORKERINSTANCE *PWORKERINSTANCE; +typedef struct WORKERINSTANCE +{ + /** Pointer to the next worker instance. */ + PWORKERINSTANCE pNext; + /** Pointer to the previous worker instance. */ + PWORKERINSTANCE pPrev; + /** Pointer to the next worker with the same pid hash slot. */ + PWORKERINSTANCE pNextPidHash; + /** 32 or 64. */ + unsigned cBits; + /** The process ID of the kWorker process. */ + pid_t pid; + union + { + struct + { + /** The exit code. */ + int32_t rcExit; + /** Set to 1 if the worker is exiting. */ + uint8_t bWorkerExiting; + uint8_t abUnused[3]; + } s; + uint8_t ab[8]; + } Result; + /** Number of result bytes read alread. */ + size_t cbResultRead; + +#ifdef KBUILD_OS_WINDOWS + /** The process handle. */ + HANDLE hProcess; + /** The bi-directional pipe we use to talk to the kWorker process. */ + HANDLE hPipe; + /** For overlapped read (have valid event semaphore). */ + OVERLAPPED OverlappedRead; +# ifdef CONFIG_NEW_WIN_CHILDREN + /** Standard output catcher (reused). */ + PWINCCWPIPE pStdOut; + /** Standard error catcher (reused). */ + PWINCCWPIPE pStdErr; +# endif +#else + /** The socket descriptor we use to talk to the kWorker process. */ + int fdSocket; +#endif + + /** --debug-dump-history-on-failure. */ + int fDebugDumpHistoryOnFailure; + /** Current history index (must mod with aHistory element count). */ + unsigned iHistory; + /** History. */ + struct + { + /** Pointer to the message, NULL if none. */ + void *pvMsg; + /** The message size, zero if not present. */ + size_t cbMsg; + } aHistory[4]; + + /** What it's busy with. NULL if idle. */ + struct child *pBusyWith; +} WORKERINSTANCE; + + +typedef struct WORKERLIST +{ + /** The head of the list. NULL if empty. */ + PWORKERINSTANCE pHead; + /** The tail of the list. NULL if empty. */ + PWORKERINSTANCE pTail; + /** Number of list entries. */ + size_t cEntries; +} WORKERLIST; +typedef WORKERLIST *PWORKERLIST; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** List of idle worker.*/ +static WORKERLIST g_IdleList; +/** List of busy workers. */ +static WORKERLIST g_BusyList; +/** PID hash table for the workers. + * @sa KWORKER_PID_HASH() */ +static PWORKERINSTANCE g_apPidHash[61]; + +#ifdef KBUILD_OS_WINDOWS +/** For naming the pipes. + * Also indicates how many worker instances we've spawned. */ +static unsigned g_uWorkerSeqNo = 0; +#endif +/** Set if we've registred the atexit handler already. */ +static int g_fAtExitRegistered = 0; + +/** @var g_cArchBits + * The bit count of the architecture this binary is compiled for. */ +/** @var g_szArch + * The name of the architecture this binary is compiled for. */ +/** @var g_cArchBits + * The bit count of the alternative architecture. */ +/** @var g_szAltArch + * The name of the alternative architecture. */ +#if defined(KBUILD_ARCH_AMD64) +static unsigned g_cArchBits = 64; +static char const g_szArch[] = "amd64"; +static unsigned g_cAltArchBits = 32; +static char const g_szAltArch[] = "x86"; +#elif defined(KBUILD_ARCH_X86) +static unsigned g_cArchBits = 32; +static char const g_szArch[] = "x86"; +static unsigned g_cAltArchBits = 64; +static char const g_szAltArch[] = "amd64"; +#else +# error "Port me!" +#endif + +#ifdef KBUILD_OS_WINDOWS +/** The processor group allocator state. */ +static MKWINCHILDCPUGROUPALLOCSTATE g_SubmitProcessorGroupAllocator; +# if K_ARCH_BITS == 64 +/** The processor group allocator state for 32-bit processes. */ +static MKWINCHILDCPUGROUPALLOCSTATE g_SubmitProcessorGroupAllocator32; +# endif +#endif + +#ifdef KBUILD_OS_WINDOWS +/** Pointer to kernel32!SetThreadGroupAffinity. */ +static BOOL (WINAPI *g_pfnSetThreadGroupAffinity)(HANDLE, const GROUP_AFFINITY*, GROUP_AFFINITY *); +#endif + + + +/** + * Unlinks a worker instance from a list. + * + * @param pList The list. + * @param pWorker The worker. + */ +static void kSubmitListUnlink(PWORKERLIST pList, PWORKERINSTANCE pWorker) +{ + PWORKERINSTANCE pNext = pWorker->pNext; + PWORKERINSTANCE pPrev = pWorker->pPrev; + + if (pNext) + { + assert(pNext->pPrev == pWorker); + pNext->pPrev = pPrev; + } + else + { + assert(pList->pTail == pWorker); + pList->pTail = pPrev; + } + + if (pPrev) + { + assert(pPrev->pNext == pWorker); + pPrev->pNext = pNext; + } + else + { + assert(pList->pHead == pWorker); + pList->pHead = pNext; + } + + assert(!pList->pHead || pList->pHead->pPrev == NULL); + assert(!pList->pTail || pList->pTail->pNext == NULL); + + assert(pList->cEntries > 0); + pList->cEntries--; + + pWorker->pNext = NULL; + pWorker->pPrev = NULL; +} + + +/** + * Appends a worker instance to the tail of a list. + * + * @param pList The list. + * @param pWorker The worker. + */ +static void kSubmitListAppend(PWORKERLIST pList, PWORKERINSTANCE pWorker) +{ + PWORKERINSTANCE pTail = pList->pTail; + + assert(pTail != pWorker); + assert(pList->pHead != pWorker); + + pWorker->pNext = NULL; + pWorker->pPrev = pTail; + if (pTail != NULL) + { + assert(pTail->pNext == NULL); + pTail->pNext = pWorker; + } + else + { + assert(pList->pHead == NULL); + pList->pHead = pWorker; + } + pList->pTail = pWorker; + + assert(pList->pHead->pPrev == NULL); + assert(pList->pTail->pNext == NULL); + + pList->cEntries++; +} + + +/** + * Remove worker from the process ID hash table. + * + * @param pWorker The worker. + */ +static void kSubmitPidHashRemove(PWORKERINSTANCE pWorker) +{ + size_t idxHash = KWORKER_PID_HASH(pWorker->pid); + if (g_apPidHash[idxHash] == pWorker) + g_apPidHash[idxHash] = pWorker->pNext; + else + { + PWORKERINSTANCE pPrev = g_apPidHash[idxHash]; + while (pPrev && pPrev->pNext != pWorker) + pPrev = pPrev->pNext; + assert(pPrev != NULL); + if (pPrev) + pPrev->pNext = pWorker->pNext; + } + pWorker->pid = -1; +} + + +/** + * Looks up a worker by its process ID. + * + * @returns Pointer to the worker instance if found. NULL if not. + * @param pid The process ID of the worker. + */ +static PWORKERINSTANCE kSubmitFindWorkerByPid(pid_t pid) +{ + PWORKERINSTANCE pWorker = g_apPidHash[KWORKER_PID_HASH(pid)]; + while (pWorker && pWorker->pid != pid) + pWorker = pWorker->pNextPidHash; + return pWorker; +} + + +/** + * Calcs the path to the kWorker binary for the worker. + * + * @returns + * @param pCtx The command execution context. + * @param pWorker The worker (for its bitcount). + * @param pszExecutable The output buffer. + * @param cbExecutable The output buffer size. + */ +static int kSubmitCalcExecutablePath(PKMKBUILTINCTX pCtx, PWORKERINSTANCE pWorker, char *pszExecutable, size_t cbExecutable) +{ +#if defined(KBUILD_OS_WINDOWS) || defined(KBUILD_OS_OS2) + static const char s_szWorkerName[] = "kWorker.exe"; +#else + static const char s_szWorkerName[] = "kWorker"; +#endif + const char *pszBinPath = get_kbuild_bin_path(); + size_t const cchBinPath = strlen(pszBinPath); + size_t cchExecutable; + if ( pWorker->cBits == g_cArchBits + ? cchBinPath + 1 + sizeof(s_szWorkerName) <= cbExecutable + : cchBinPath + 1 - sizeof(g_szArch) + sizeof(g_szAltArch) + sizeof(s_szWorkerName) <= cbExecutable ) + { + memcpy(pszExecutable, pszBinPath, cchBinPath); + cchExecutable = cchBinPath; + + /* Replace the arch bin directory extension with the alternative one if requested. */ + if (pWorker->cBits != g_cArchBits) + { + if ( cchBinPath < sizeof(g_szArch) + || memcmp(&pszExecutable[cchBinPath - sizeof(g_szArch) + 1], g_szArch, sizeof(g_szArch) - 1) != 0) + return errx(pCtx, 1, "KBUILD_BIN_PATH does not end with main architecture (%s) as expected: %s", + pszBinPath, g_szArch); + cchExecutable -= sizeof(g_szArch) - 1; + memcpy(&pszExecutable[cchExecutable], g_szAltArch, sizeof(g_szAltArch) - 1); + cchExecutable += sizeof(g_szAltArch) - 1; + } + + /* Append a slash and the worker name. */ + pszExecutable[cchExecutable++] = '/'; + memcpy(&pszExecutable[cchExecutable], s_szWorkerName, sizeof(s_szWorkerName)); + return 0; + } + return errx(pCtx, 1, "KBUILD_BIN_PATH is too long"); +} + + +#ifdef KBUILD_OS_WINDOWS +/** + * Calcs the UTF-16 path to the kWorker binary for the worker. + * + * @returns + * @param pCtx The command execution context. + * @param pWorker The worker (for its bitcount). + * @param pwszExecutable The output buffer. + * @param cwcExecutable The output buffer size. + */ +static int kSubmitCalcExecutablePathW(PKMKBUILTINCTX pCtx, PWORKERINSTANCE pWorker, wchar_t *pwszExecutable, size_t cwcExecutable) +{ + char szExecutable[MAX_PATH]; + int rc = kSubmitCalcExecutablePath(pCtx, pWorker, szExecutable, sizeof(szExecutable)); + if (rc == 0) + { + int cwc = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, szExecutable, strlen(szExecutable) + 1, + pwszExecutable, cwcExecutable); + if (cwc > 0) + return 0; + return errx(pCtx, 1, "MultiByteToWideChar failed on '%s': %u", szExecutable, GetLastError()); + } + return rc; +} +#endif + + +/** + * Creates a new worker process. + * + * @returns 0 on success, non-zero value on failure. + * @param pCtx The command execution context. + * @param pWorker The worker structure. Caller does the linking + * (as we might be reusing an existing worker + * instance because a worker shut itself down due + * to high resource leak level). + * @param cVerbosity The verbosity level. + */ +static int kSubmitSpawnWorker(PKMKBUILTINCTX pCtx, PWORKERINSTANCE pWorker, int cVerbosity) +{ + int rc; +#ifdef KBUILD_OS_WINDOWS + wchar_t wszExecutable[MAX_PATH]; +#else + PATH_VAR(szExecutable); +#endif + + /* + * Get the output path so it can be passed on as a volatile. + */ + const char *pszVarVolatile; + struct variable *pVarVolatile = lookup_variable(TUPLE("PATH_OUT")); + if (pVarVolatile) + pszVarVolatile = "PATH_OUT"; + else + { + pVarVolatile = lookup_variable(TUPLE("PATH_OUT_BASE")); + if (pVarVolatile) + pszVarVolatile = "PATH_OUT_BASE"; + else + warn(pCtx, "Neither PATH_OUT_BASE nor PATH_OUT was found."); + } + if (pVarVolatile && strchr(pVarVolatile->value, '"')) + return errx(pCtx, -1, "%s contains double quotes.", pszVarVolatile); + if (pVarVolatile && strlen(pVarVolatile->value) >= GET_PATH_MAX) + return errx(pCtx, -1, "%s is too long (max %u)", pszVarVolatile, GET_PATH_MAX); + + /* + * Construct the executable path. + */ +#ifdef KBUILD_OS_WINDOWS + rc = kSubmitCalcExecutablePathW(pCtx, pWorker, wszExecutable, K_ELEMENTS(wszExecutable)); +#else + rc = kSubmitCalcExecutablePath(pCtx, pWorker, szExecutable, GET_PATH_MAX); +#endif + if (rc == 0) + { +#ifdef KBUILD_OS_WINDOWS + static DWORD s_fDenyRemoteClients = ~(DWORD)0; + wchar_t wszPipeName[128]; + HANDLE hWorkerPipe; + int iProcessorGroup; + +# if K_ARCH_BITS == 64 + /** @todo make it return -1 if not applicable (e.g only one group). */ + if (pWorker->cBits != 32) + iProcessorGroup = MkWinChildAllocateCpuGroup(&g_SubmitProcessorGroupAllocator); + else + iProcessorGroup = MkWinChildAllocateCpuGroup(&g_SubmitProcessorGroupAllocator32); +# else + iProcessorGroup = MkWinChildAllocateCpuGroup(&g_SubmitProcessorGroupAllocator); +# endif + + /* + * Create the bi-directional pipe with overlapping I/O enabled. + */ + if (s_fDenyRemoteClients == ~(DWORD)0) + s_fDenyRemoteClients = GetVersion() >= 0x60000 ? PIPE_REJECT_REMOTE_CLIENTS : 0; + _snwprintf(wszPipeName, sizeof(wszPipeName), L"\\\\.\\pipe\\kmk-%u-kWorker-%u-%u", + GetCurrentProcessId(), g_uWorkerSeqNo++, GetTickCount()); + hWorkerPipe = CreateNamedPipeW(wszPipeName, + PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED | FILE_FLAG_FIRST_PIPE_INSTANCE /* win2k sp2+ */, + PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT | s_fDenyRemoteClients, + 1 /* cMaxInstances */, + 64 /*cbOutBuffer*/, + 65536 /*cbInBuffer*/, + 0 /*cMsDefaultTimeout -> 50ms*/, + NULL /* pSecAttr - no inherit */); + if (hWorkerPipe != INVALID_HANDLE_VALUE) + { + pWorker->hPipe = CreateFileW(wszPipeName, + GENERIC_READ | GENERIC_WRITE, + 0 /* dwShareMode - no sharing */, + NULL /*pSecAttr - no inherit */, + OPEN_EXISTING, + FILE_FLAG_OVERLAPPED, + NULL /*hTemplate*/); + if (pWorker->hPipe != INVALID_HANDLE_VALUE) + { + pWorker->OverlappedRead.hEvent = CreateEventW(NULL /*pSecAttrs - no inherit*/, TRUE /*bManualReset*/, + TRUE /*bInitialState*/, NULL /*pwszName*/); + if (pWorker->OverlappedRead.hEvent != NULL) + { + extern int process_priority; /* main.c */ + wchar_t wszCommandLine[MAX_PATH * 3 + 32]; + wchar_t *pwszDst = wszCommandLine; + size_t cwcDst = K_ELEMENTS(wszCommandLine); + int cwc; + DWORD fFlags; + STARTUPINFOW StartupInfo; + PROCESS_INFORMATION ProcInfo = { NULL, NULL, 0, 0 }; + + /* + * Compose the command line. + */ + cwc = _snwprintf(pwszDst, cwcDst, L"\"%s\" ", wszExecutable); + assert(cwc > 0 && cwc < cwcDst); + pwszDst += cwc; + cwcDst -= cwc; + if (pVarVolatile && *pVarVolatile->value) + { + char chEnd = strchr(pVarVolatile->value, '\0')[-1]; + if (chEnd == '\\') + cwc = _snwprintf(pwszDst, cwcDst, L" --volatile \"%S.\"", pVarVolatile->value); + else + cwc = _snwprintf(pwszDst, cwcDst, L" --volatile \"%S\"", pVarVolatile->value); + assert(cwc > 0 && cwc < cwcDst); + pwszDst += cwc; + cwcDst -= cwc; + } + if (iProcessorGroup >= 0) + { + cwc = _snwprintf(pwszDst, cwcDst, L" --group %d", iProcessorGroup); + assert(cwc > 0 && cwc < cwcDst); + pwszDst += cwc; + cwcDst -= cwc; + } + *pwszDst = '\0'; + + /* + * Fill in the startup information. + */ + memset(&StartupInfo, 0, sizeof(StartupInfo)); + StartupInfo.cb = sizeof(StartupInfo); + GetStartupInfoW(&StartupInfo); + StartupInfo.dwFlags &= ~STARTF_USESTDHANDLES; + StartupInfo.lpReserved2 = NULL; + StartupInfo.cbReserved2 = 0; + + /* + * Flags and such. + */ + fFlags = CREATE_SUSPENDED; + switch (process_priority) + { + case 1: fFlags |= CREATE_SUSPENDED | IDLE_PRIORITY_CLASS; break; + case 2: fFlags |= CREATE_SUSPENDED | BELOW_NORMAL_PRIORITY_CLASS; break; + case 3: fFlags |= CREATE_SUSPENDED | NORMAL_PRIORITY_CLASS; break; + case 4: fFlags |= CREATE_SUSPENDED | HIGH_PRIORITY_CLASS; break; + case 5: fFlags |= CREATE_SUSPENDED | REALTIME_PRIORITY_CLASS; break; + } + + /* + * Create the worker process. + */ + if (CreateProcessW(wszExecutable, wszCommandLine, NULL /*pProcSecAttr*/, NULL /*pThreadSecAttr*/, + FALSE /*fInheritHandles*/, fFlags, NULL /*pwszzEnvironment*/, + NULL /*pwszCwd*/, &StartupInfo, &ProcInfo)) + { + char szErrMsg[256]; + BOOL afReplace[3] = { TRUE, FALSE, FALSE }; + HANDLE ahReplace[3] = { hWorkerPipe, INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE }; + if (pWorker->pStdOut) + { + afReplace[1] = TRUE; + afReplace[2] = TRUE; + ahReplace[1] = pWorker->pStdOut->hPipeChild; + ahReplace[2] = pWorker->pStdErr->hPipeChild; + } + + rc = nt_child_inject_standard_handles(ProcInfo.hProcess, afReplace, ahReplace, szErrMsg, sizeof(szErrMsg)); + if (rc == 0) + { + BOOL fRet; + switch (process_priority) + { + case 1: fRet = SetThreadPriority(ProcInfo.hThread, THREAD_PRIORITY_IDLE); break; + case 2: fRet = SetThreadPriority(ProcInfo.hThread, THREAD_PRIORITY_BELOW_NORMAL); break; + case 3: fRet = SetThreadPriority(ProcInfo.hThread, THREAD_PRIORITY_NORMAL); break; + case 4: fRet = SetThreadPriority(ProcInfo.hThread, THREAD_PRIORITY_HIGHEST); break; + case 5: fRet = SetThreadPriority(ProcInfo.hThread, THREAD_PRIORITY_TIME_CRITICAL); break; + default: fRet = TRUE; + } + if (!fRet) + warnx(pCtx, "warning: failed to set kWorker thread priority: %u\n", GetLastError()); + + if (iProcessorGroup >= 0 && g_pfnSetThreadGroupAffinity) + { + GROUP_AFFINITY OldAff = { 0, 0, 0, 0, 0 }; + GROUP_AFFINITY NewAff = { 0 /* == all active apparently */, (WORD)iProcessorGroup, 0, 0, 0 }; + if (!g_pfnSetThreadGroupAffinity(ProcInfo.hThread, &NewAff, &OldAff)) + warnx(pCtx, "warning: Failed to set processor group to %d: %u\n", + iProcessorGroup, GetLastError()); + } + + /* + * Now, we just need to resume the thread. + */ + if (ResumeThread(ProcInfo.hThread)) + { + CloseHandle(hWorkerPipe); + CloseHandle(ProcInfo.hThread); + pWorker->pid = ProcInfo.dwProcessId; + pWorker->hProcess = ProcInfo.hProcess; + if (cVerbosity > 0) + warnx(pCtx, "created %d bit worker %d\n", pWorker->cBits, pWorker->pid); + return 0; + } + + /* + * Failed, bail out. + */ + rc = errx(pCtx, -3, "ResumeThread failed: %u", GetLastError()); + } + else + rc = errx(pCtx, -3, "%s", szErrMsg); + TerminateProcess(ProcInfo.hProcess, 1234); + CloseHandle(ProcInfo.hThread); + CloseHandle(ProcInfo.hProcess); + } + else + rc = errx(pCtx, -2, "CreateProcessW failed: %u (exe=%S cmdline=%S)", + GetLastError(), wszExecutable, wszCommandLine); + CloseHandle(pWorker->OverlappedRead.hEvent); + pWorker->OverlappedRead.hEvent = INVALID_HANDLE_VALUE; + } + else + rc = errx(pCtx, -1, "CreateEventW failed: %u", GetLastError()); + CloseHandle(pWorker->hPipe); + pWorker->hPipe = INVALID_HANDLE_VALUE; + } + else + rc = errx(pCtx, -1, "Opening named pipe failed: %u", GetLastError()); + CloseHandle(hWorkerPipe); + } + else + rc = errx(pCtx, -1, "CreateNamedPipeW failed: %u", GetLastError()); + +#else + /* + * Create a socket pair. + */ + int aiPair[2] = { -1, -1 }; + if (socketpair(AF_LOCAL, SOCK_STREAM, 0, aiPair) == 0) + { + pWorker->fdSocket = aiPair[1]; + + rc = -1; + } + else + rc = err(pCtx, -1, "socketpair"); +#endif + } + else + rc = errx(pCtx, -1, "KBUILD_BIN_PATH is too long"); + return rc; +} + + +/** + * Selects an idle worker or spawns a new one. + * + * @returns Pointer to the selected worker instance. NULL on error. + * @param pCtx The command execution context. + * @param pWorker The idle worker instance to respawn. + * On failure this will be freed! + * @param cBitsWorker The worker bitness - 64 or 32. + */ +static int kSubmitRespawnWorker(PKMKBUILTINCTX pCtx, PWORKERINSTANCE pWorker, int cVerbosity) +{ + /* + * Clean up after the old worker. + */ +#ifdef KBUILD_OS_WINDOWS + DWORD rcWait; + + /* Close the pipe handle first, breaking the pipe in case it's not already + busted up. Close the event semaphore too before waiting for the process. */ + if (pWorker->hPipe != INVALID_HANDLE_VALUE) + { + if (!CloseHandle(pWorker->hPipe)) + warnx(pCtx, "CloseHandle(pWorker->hPipe): %u", GetLastError()); + pWorker->hPipe = INVALID_HANDLE_VALUE; + } + + if (!CloseHandle(pWorker->OverlappedRead.hEvent)) + warnx(pCtx, "CloseHandle(pWorker->OverlappedRead.hEvent): %u", GetLastError()); + pWorker->OverlappedRead.hEvent = INVALID_HANDLE_VALUE; + + if (pWorker->pStdOut) + MkWinChildcareWorkerDrainPipes(NULL, pWorker->pStdOut, pWorker->pStdErr); + + /* It's probably shutdown already, if not give it 10 milliseconds before + we terminate it forcefully. */ + rcWait = WaitForSingleObject(pWorker->hProcess, 10); + if (rcWait != WAIT_OBJECT_0) + { + BOOL fRc = TerminateProcess(pWorker->hProcess, 127); + + if (pWorker->pStdOut) + MkWinChildcareWorkerDrainPipes(NULL, pWorker->pStdOut, pWorker->pStdErr); + + rcWait = WaitForSingleObject(pWorker->hProcess, 100); + if (rcWait != WAIT_OBJECT_0) + warnx(pCtx, "WaitForSingleObject returns %u (and TerminateProcess %d)", rcWait, fRc); + } + + if (pWorker->pStdOut) + MkWinChildcareWorkerDrainPipes(NULL, pWorker->pStdOut, pWorker->pStdErr); + + if (!CloseHandle(pWorker->hProcess)) + warnx(pCtx, "CloseHandle(pWorker->hProcess): %u", GetLastError()); + pWorker->hProcess = INVALID_HANDLE_VALUE; + +#else + pid_t pidWait; + int rc; + + if (pWorker->fdSocket != -1) + { + if (close(pWorker->fdSocket) != 0) + warn(pCtx, "close(pWorker->fdSocket)"); + pWorker->fdSocket = -1; + } + + kill(pWorker->pid, SIGTERM); + pidWait = waitpid(pWorker->pid, &rc, 0); + if (pidWait != pWorker->pid) + warn(pCtx, "waitpid(pWorker->pid,,0)"); +#endif + + /* + * Unlink it from the hash table. + */ + kSubmitPidHashRemove(pWorker); + + /* + * Respawn it. + */ + if (kSubmitSpawnWorker(pCtx, pWorker, cVerbosity) == 0) + { + /* + * Insert it into the process ID hash table and idle list. + */ + size_t idxHash = KWORKER_PID_HASH(pWorker->pid); + pWorker->pNextPidHash = g_apPidHash[idxHash]; + g_apPidHash[idxHash] = pWorker; + return 0; + } + + kSubmitListUnlink(&g_IdleList, pWorker); + free(pWorker); + return -1; +} + + +/** + * Selects an idle worker or spawns a new one. + * + * @returns Pointer to the selected worker instance. NULL on error. + * @param cBitsWorker The worker bitness - 64 or 32. + */ +static PWORKERINSTANCE kSubmitSelectWorkSpawnNewIfNecessary(PKMKBUILTINCTX pCtx, unsigned cBitsWorker, int cVerbosity) +{ + /* + * Lookup up an idle worker. + */ + PWORKERINSTANCE pWorker = g_IdleList.pHead; + while (pWorker) + { + if (pWorker->cBits == cBitsWorker) + return pWorker; + pWorker = pWorker->pNext; + } + + /* + * Create a new worker instance. + */ + pWorker = (PWORKERINSTANCE)xcalloc(sizeof(*pWorker)); + pWorker->cBits = cBitsWorker; +#if defined(CONFIG_NEW_WIN_CHILDREN) && defined(KBUILD_OS_WINDOWS) + if (output_sync != OUTPUT_SYNC_NONE) + { + pWorker->pStdOut = MkWinChildcareCreateWorkerPipe(1, g_uWorkerSeqNo << 1); + pWorker->pStdErr = MkWinChildcareCreateWorkerPipe(2, g_uWorkerSeqNo << 1); + } + if ( output_sync == OUTPUT_SYNC_NONE + || ( pWorker->pStdOut != NULL + && pWorker->pStdErr != NULL)) +#endif + { + if (kSubmitSpawnWorker(pCtx, pWorker, cVerbosity) == 0) + { + /* + * Insert it into the process ID hash table and idle list. + */ + size_t idxHash = KWORKER_PID_HASH(pWorker->pid); + pWorker->pNextPidHash = g_apPidHash[idxHash]; + g_apPidHash[idxHash] = pWorker; + + kSubmitListAppend(&g_IdleList, pWorker); + return pWorker; + } + } +#if defined(CONFIG_NEW_WIN_CHILDREN) && defined(KBUILD_OS_WINDOWS) + if (pWorker->pStdErr) + MkWinChildcareDeleteWorkerPipe(pWorker->pStdErr); + if (pWorker->pStdOut) + MkWinChildcareDeleteWorkerPipe(pWorker->pStdOut); +#endif + + free(pWorker); + return NULL; +} + + +/** + * Composes a JOB mesage for a worker. + * + * @returns Pointer to the message. + * @param pszExecutable The executable to run. + * @param papszArgs The argument vector. + * @param papszEnvVars The environment vector. + * @param pszCwd The current directory. + * @param fWatcomBrainDamage The wcc/wcc386 workaround. + * @param fNoPchCaching Whether to disable precompiled header caching. + * @param pszSpecialEnv Environment variable (name=value) subject to + * special expansion in kWorker. NULL if none. + * @param papszPostCmdArgs The post command and it's arguments. + * @param cPostCmdArgs Number of post command argument, including the + * command. Zero if no post command scheduled. + * @param pcbMsg Where to return the message length. + */ +static void *kSubmitComposeJobMessage(const char *pszExecutable, char **papszArgs, char **papszEnvVars, + const char *pszCwd, int fWatcomBrainDamage, int fNoPchCaching, const char *pszSpecialEnv, + char **papszPostCmdArgs, uint32_t cPostCmdArgs, uint32_t *pcbMsg) +{ + size_t cbTmp; + size_t cbSpecialEnv; + uint32_t i; + uint32_t cbMsg; + uint32_t cArgs; + uint32_t cEnvVars; + uint8_t *pbMsg; + uint8_t *pbCursor; + + /* + * Adjust input. + */ + if (!pszExecutable) + pszExecutable = papszArgs[0]; + + /* + * Calculate the message length first. + */ + cbMsg = sizeof(cbMsg); + cbMsg += sizeof("JOB"); + cbMsg += strlen(pszExecutable) + 1; + cbMsg += strlen(pszCwd) + 1; + + cbMsg += sizeof(cArgs); + for (i = 0; papszArgs[i] != NULL; i++) + cbMsg += 1 + strlen(papszArgs[i]) + 1; + cArgs = i; + + cbMsg += sizeof(cArgs); + for (i = 0; papszEnvVars[i] != NULL; i++) + cbMsg += strlen(papszEnvVars[i]) + 1; + cEnvVars = i; + + cbMsg += 1; /* fWatcomBrainDamage */ + cbMsg += 1; /* fNoPchCaching */ + + cbSpecialEnv = pszSpecialEnv ? strchr(pszSpecialEnv, '=') - pszSpecialEnv : 0; + cbMsg += cbSpecialEnv + 1; + + cbMsg += sizeof(cPostCmdArgs); + for (i = 0; i < cPostCmdArgs; i++) + cbMsg += strlen(papszPostCmdArgs[i]) + 1; + + /* + * Compose the message. + */ + pbMsg = pbCursor = xmalloc(cbMsg); + + /* header */ + memcpy(pbCursor, &cbMsg, sizeof(cbMsg)); + pbCursor += sizeof(cbMsg); + memcpy(pbCursor, "JOB", sizeof("JOB")); + pbCursor += sizeof("JOB"); + + /* executable. */ + cbTmp = strlen(pszExecutable) + 1; + memcpy(pbCursor, pszExecutable, cbTmp); + pbCursor += cbTmp; + + /* cwd */ + cbTmp = strlen(pszCwd) + 1; + memcpy(pbCursor, pszCwd, cbTmp); + pbCursor += cbTmp; + + /* arguments */ + memcpy(pbCursor, &cArgs, sizeof(cArgs)); + pbCursor += sizeof(cArgs); + for (i = 0; papszArgs[i] != NULL; i++) + { + *pbCursor++ = 0; /* Argument expansion flags (MSC, EMX). */ + cbTmp = strlen(papszArgs[i]) + 1; + memcpy(pbCursor, papszArgs[i], cbTmp); + pbCursor += cbTmp; + } + assert(i == cArgs); + + /* environment */ + memcpy(pbCursor, &cEnvVars, sizeof(cEnvVars)); + pbCursor += sizeof(cEnvVars); + for (i = 0; papszEnvVars[i] != NULL; i++) + { + cbTmp = strlen(papszEnvVars[i]) + 1; + memcpy(pbCursor, papszEnvVars[i], cbTmp); + pbCursor += cbTmp; + } + assert(i == cEnvVars); + + /* flags */ + *pbCursor++ = fWatcomBrainDamage != 0; + *pbCursor++ = fNoPchCaching != 0; + + /* Special environment variable name. */ + memcpy(pbCursor, pszSpecialEnv, cbSpecialEnv); + pbCursor += cbSpecialEnv; + *pbCursor++ = '\0'; + + /* post command */ + memcpy(pbCursor, &cPostCmdArgs, sizeof(cPostCmdArgs)); + pbCursor += sizeof(cPostCmdArgs); + for (i = 0; i < cPostCmdArgs; i++) + { + cbTmp = strlen(papszPostCmdArgs[i]) + 1; + memcpy(pbCursor, papszPostCmdArgs[i], cbTmp); + pbCursor += cbTmp; + } + assert(i == cPostCmdArgs); + + assert(pbCursor - pbMsg == (size_t)cbMsg); + + /* + * Done. + */ + *pcbMsg = cbMsg; + return pbMsg; +} + + +/** + * Sends the job message to the given worker, respawning the worker if + * necessary. + * + * @returns 0 on success, non-zero on failure. + * + * @param pCtx The command execution context. + * @param pWorker The work to send the request to. The worker is + * on the idle list. + * @param pvMsg The message to send. + * @param cbMsg The size of the message. + * @param fNoRespawning Set if + * @param cVerbosity The verbosity level. + */ +static int kSubmitSendJobMessage(PKMKBUILTINCTX pCtx, PWORKERINSTANCE pWorker, void const *pvMsg, uint32_t cbMsg, + int fNoRespawning, int cVerbosity) +{ + int cRetries; + + /* + * Respawn the worker if it stopped by itself and we closed the pipe already. + */ +#ifdef KBUILD_OS_WINDOWS + if (pWorker->hPipe == INVALID_HANDLE_VALUE) +#else + if (pWorker->fdSocket == -1) +#endif + { + if (!fNoRespawning) + { + if (cVerbosity > 0) + warnx(pCtx, "Respawning worker (#1)...\n"); + if (kSubmitRespawnWorker(pCtx, pWorker, cVerbosity) != 0) + return 2; + } + + } + + /* + * Restart-on-broken-pipe loop. Necessary? + */ + for (cRetries = !fNoRespawning ? 1 : 0; ; cRetries--) + { + /* + * Try write the message. + */ + uint32_t cbLeft = cbMsg; + uint8_t const *pbLeft = (uint8_t const *)pvMsg; +#ifdef KBUILD_OS_WINDOWS + DWORD dwErr; + DWORD cbWritten; + while (WriteFile(pWorker->hPipe, pbLeft, cbLeft, &cbWritten, NULL /*pOverlapped*/)) + { + assert(cbWritten <= cbLeft); + cbLeft -= cbWritten; + if (!cbLeft) + return 0; + + /* This scenario shouldn't really ever happen. But just in case... */ + pbLeft += cbWritten; + } + dwErr = GetLastError(); + if ( ( dwErr != ERROR_BROKEN_PIPE + && dwErr != ERROR_NO_DATA) + || cRetries <= 0) + return errx(pCtx, 1, "Error writing to worker: %u", dwErr); +#else + ssize_t cbWritten + while ((cbWritten = write(pWorker->fdSocket, pbLeft, cbLeft)) >= 0) + { + assert(cbWritten <= cbLeft); + cbLeft -= cbWritten; + if (!cbLeft) + return 0; + + pbLeft += cbWritten; + } + if ( ( errno != EPIPE + && errno != ENOTCONN + && errno != ECONNRESET)) + || cRetries <= 0) + return err(pCtx, 1, "Error writing to worker"); +# error "later" +#endif + + /* + * Broken connection. Try respawn the worker. + */ + if (cVerbosity > 0) + warnx(pCtx, "Respawning worker (#2)...\n"); + if (kSubmitRespawnWorker(pCtx, pWorker, cVerbosity) != 0) + return 2; + } +} + + +/** + * Closes the connection on a worker that said it is going to exit now. + * + * This is a way of dealing with imperfect resource management in the worker, it + * will monitor it a little and trigger a respawn when it looks bad. + * + * This function just closes the pipe / socket connection to the worker. The + * kSubmitSendJobMessage function will see this a trigger a respawn the next + * time the worker is engaged. This will usually mean there's a little delay in + * which the process can terminate without us having to actively wait for it. + * + * @param pCtx The command execution context. + * @param pWorker The worker instance. + */ +static void kSubmitCloseConnectOnExitingWorker(PKMKBUILTINCTX pCtx, PWORKERINSTANCE pWorker) +{ +#ifdef KBUILD_OS_WINDOWS + if (!CloseHandle(pWorker->hPipe)) + warnx(pCtx, "CloseHandle(pWorker->hPipe): %u", GetLastError()); + pWorker->hPipe = INVALID_HANDLE_VALUE; +#else + if (close(pWorker->fdSocket) != 0) + warn(pCtx, "close(pWorker->fdSocket)"); + pWorker->fdSocket = -1; +#endif +} + + +#ifdef KBUILD_OS_WINDOWS + +/** + * Handles read failure. + * + * @returns Exit code. + * @param pCtx The command execution context. + * @param pWorker The worker instance. + * @param dwErr The error code. + * @param pszWhere Where it failed. + */ +static int kSubmitWinReadFailed(PKMKBUILTINCTX pCtx, PWORKERINSTANCE pWorker, DWORD dwErr, const char *pszWhere) +{ + DWORD dwExitCode; + + if (pWorker->cbResultRead == 0) + errx(pCtx, 1, "%s/ReadFile failed: %u", pszWhere, dwErr); + else + errx(pCtx, 1, "%s/ReadFile failed: %u (read %u bytes)", pszWhere, dwErr, pWorker->cbResultRead); + assert(dwErr != 0); + + /* Complete the result. */ + pWorker->Result.s.rcExit = 127; + pWorker->Result.s.bWorkerExiting = 1; + pWorker->cbResultRead = sizeof(pWorker->Result); + + if (GetExitCodeProcess(pWorker->hProcess, &dwExitCode)) + { + if (dwExitCode != 0) + pWorker->Result.s.rcExit = dwExitCode; + } + + return dwErr != 0 ? (int)(dwErr & 0x7fffffff) : 0x7fffffff; + +} + + +/** + * Used by + * @returns 0 if we got the whole result, -1 if I/O is pending, and windows last + * error on ReadFile failure. + * @param pCtx The command execution context. + * @param pWorker The worker instance. + */ +static int kSubmitReadMoreResultWin(PKMKBUILTINCTX pCtx, PWORKERINSTANCE pWorker, const char *pszWhere) +{ + /* + * Set up the result read, telling the sub_proc.c unit about it. + */ + while (pWorker->cbResultRead < sizeof(pWorker->Result)) + { + DWORD cbRead = 0; + + BOOL fRc = ResetEvent(pWorker->OverlappedRead.hEvent); + assert(fRc); (void)fRc; + + pWorker->OverlappedRead.Offset = 0; + pWorker->OverlappedRead.OffsetHigh = 0; + + if (!ReadFile(pWorker->hPipe, &pWorker->Result.ab[pWorker->cbResultRead], + sizeof(pWorker->Result) - pWorker->cbResultRead, + &cbRead, + &pWorker->OverlappedRead)) + { + DWORD dwErr = GetLastError(); + if (dwErr == ERROR_IO_PENDING) + return -1; + return kSubmitWinReadFailed(pCtx, pWorker, dwErr, pszWhere); + } + + pWorker->cbResultRead += cbRead; + assert(pWorker->cbResultRead <= sizeof(pWorker->Result)); + } + return 0; +} + +#endif /* KBUILD_OS_WINDOWS */ + + +/** + * Adds the given message to the history. + * + * @returns Pointer to old message, or NULL if no old msg to free. + * @param pWorker The worker instance. + * @param pvMsg The message. + * @param cbMsg The message size. + */ +static void *kSubmitUpdateHistory(PWORKERINSTANCE pWorker, void *pvMsg, size_t cbMsg) +{ + unsigned iHistory = pWorker->iHistory % K_ELEMENTS(pWorker->aHistory); + void *pvRet; + pWorker->iHistory++; + pvRet = pWorker->aHistory[iHistory].pvMsg; + pWorker->aHistory[iHistory].pvMsg = pvMsg; + pWorker->aHistory[iHistory].cbMsg = cbMsg; + return pvRet; +} + +typedef struct HISTORYDUMPBUF +{ + char *pszBuf; + size_t cbBuf; + size_t off; + PKMKBUILTINCTX pCtx; +} HISTORYDUMPBUF; + + +static void kSubmitDumpHistoryWrite(HISTORYDUMPBUF *pBuf, const char *pch, size_t cch) +{ + if (pBuf->off + cch >= pBuf->cbBuf) + { + size_t cbNew = pBuf->cbBuf ? pBuf->cbBuf * 2 : 65536; + while (pBuf->off + cch >= cbNew) + cbNew *= 2; + pBuf->pszBuf = (char *)xrealloc(pBuf->pszBuf, cbNew); + pBuf->cbBuf = cbNew; + } + + memcpy(&pBuf->pszBuf[pBuf->off], pch, cch); + pBuf->off += cch; + pBuf->pszBuf[pBuf->off] = '\0'; +} + +static void kSubmitDumpHistoryPrintf(HISTORYDUMPBUF *pBuf, const char *pszFormat, ...) +{ + char szTmp[32]; + va_list va; + va_start(va, pszFormat); + for (;;) + { + const char *pszPct = strchr(pszFormat, '%'); + if (!pszPct) + { + kSubmitDumpHistoryWrite(pBuf, pszFormat, strlen(pszFormat)); + return; + } + if (pszPct != pszFormat) + { + kSubmitDumpHistoryWrite(pBuf, pszFormat, pszPct - pszFormat); + pszFormat = pszPct; + } + pszFormat++; + switch (*pszFormat++) + { + case 's': + { + const char * const psz = va_arg(va, const char *); + size_t const cch = strlen(psz); + if (cch == 0 || memchr(psz, '\'', cch)) + { + kSubmitDumpHistoryWrite(pBuf, TUPLE("\"")); /** @todo what if there are '"' in the string? */ + kSubmitDumpHistoryWrite(pBuf, psz, cch); + kSubmitDumpHistoryWrite(pBuf, TUPLE("\"")); + } + else if ( !memchr(psz, ' ', cch) + && !memchr(psz, '\\', cch) + && !memchr(psz, '\t', cch) + && !memchr(psz, '\n', cch) + && !memchr(psz, '\r', cch) + && !memchr(psz, '&', cch) + && !memchr(psz, ';', cch) + && !memchr(psz, '|', cch)) + kSubmitDumpHistoryWrite(pBuf, psz, cch); + else + { + kSubmitDumpHistoryWrite(pBuf, TUPLE("'")); + kSubmitDumpHistoryWrite(pBuf, psz, strlen(psz)); + kSubmitDumpHistoryWrite(pBuf, TUPLE("'")); + } + break; + } + + case 'd': + { + int iValue = va_arg(va, int); + kSubmitDumpHistoryWrite(pBuf, szTmp, snprintf(szTmp, sizeof(szTmp), "%d", iValue)); + break; + } + + case 'u': + { + unsigned uValue = va_arg(va, unsigned); + kSubmitDumpHistoryWrite(pBuf, szTmp, snprintf(szTmp, sizeof(szTmp), "%u", uValue)); + break; + } + + case '%': + kSubmitDumpHistoryWrite(pBuf, "%s", 1); + break; + + default: + assert(0); + } + } + va_end(va); +} + +static void kSubmitDumpHistoryFlush(HISTORYDUMPBUF *pBuf) +{ + if (pBuf->off > 0) + output_write_text(pBuf->pCtx->pOut, 1, pBuf->pszBuf, pBuf->off); + pBuf->off = 0; +} + +/** + * Dumps the history for this worker to stderr in the given context. + * + * @param pCtx The command execution context. (Typically not a + * real context.) + * @param pWorker The worker instance. + */ +static void kSubmitDumpHistory(PKMKBUILTINCTX pCtx, PWORKERINSTANCE pWorker) +{ + HISTORYDUMPBUF Buf = { NULL, 0, 0, pCtx }; + int iHistory = pWorker->iHistory; + unsigned cDumped = 0; + + while (cDumped < K_ELEMENTS(pWorker->aHistory) && iHistory > 0) + { + unsigned const idx = (unsigned)--iHistory % K_ELEMENTS(pWorker->aHistory); + const char *pszMsg = (const char *)pWorker->aHistory[idx].pvMsg; + ssize_t cbMsg = pWorker->aHistory[idx].cbMsg; + const char *pszExe; + const char *pszCwd; + uint32_t i; + uint32_t cArgs; + const char *pszArgs; + size_t cbArgs; + uint32_t cEnvVars; + const char *pszEnvVars; + size_t cbEnvVars; + const char *pszSpecialEnv; + char fNoPchCaching; + char fWatcomBrainDamage; + uint32_t cPostArgs; + const char *pszPostArgs; + size_t cbPostArgs; + + cDumped++; + if (!pszMsg || !cbMsg) + break; + +#define SKIP_BYTES(a_cbSkip) do { pszMsg += (a_cbSkip); cbMsg -= (a_cbSkip); } while (0) +#define SKIP_STR() do { size_t const cbToSkip = strlen(pszMsg) + 1; SKIP_BYTES(cbToSkip); } while (0) +#define SKIP_STRING_ARRAY(a_cStrings, a_cbPreable) do { \ + for (i = 0; i < (a_cStrings) && cbMsg > 0; i++) { \ + size_t const cbToSkip = (a_cbPreable) + strlen(pszMsg + (a_cbPreable)) + 1; \ + SKIP_BYTES(cbToSkip); \ + } } while (0) + + /* Decode it: */ + SKIP_BYTES(sizeof(uint32_t) + sizeof("JOB")); + pszExe = pszMsg; + SKIP_STR(); + pszCwd = pszMsg; + SKIP_STR(); + + cArgs = *(uint32_t *)pszMsg; + SKIP_BYTES(sizeof(uint32_t)); + pszArgs = pszMsg; + SKIP_STRING_ARRAY(cArgs, 1 /*fbFlags*/); + cbArgs = pszMsg - pszArgs; + + cEnvVars = *(uint32_t *)pszMsg; + SKIP_BYTES(sizeof(uint32_t)); + pszEnvVars = pszMsg; + SKIP_STRING_ARRAY(cEnvVars, 0); + cbEnvVars = pszMsg - pszEnvVars; + + fWatcomBrainDamage = pszMsg[0] != '\0'; + fNoPchCaching = pszMsg[1] != '\0'; + SKIP_BYTES(2); + + pszSpecialEnv = pszMsg; + SKIP_STR(); + + cPostArgs = *(uint32_t *)pszMsg; + SKIP_BYTES(sizeof(uint32_t)); + pszPostArgs = pszMsg; + SKIP_STRING_ARRAY(cPostArgs, 0); + cbPostArgs = pszMsg - pszPostArgs; + + /* Produce parseable output: */ + kSubmitDumpHistoryPrintf(&Buf, "kWorker %u/%u:\n\tkSubmit", (long)pWorker->pid, iHistory, pszExe); + if (fNoPchCaching) + kSubmitDumpHistoryWrite(&Buf, TUPLE(" --no-pch-caching")); + if (fWatcomBrainDamage) + kSubmitDumpHistoryWrite(&Buf, TUPLE(" --watcom-brain-damage")); + if (pszSpecialEnv) + kSubmitDumpHistoryPrintf(&Buf, " --special-env %s", pszSpecialEnv); + kSubmitDumpHistoryPrintf(&Buf, " --chdir %s \\\n", pszCwd); + + pszMsg = pszEnvVars; + cbMsg = cbEnvVars; + for (i = 0; i < cEnvVars && cbMsg > 0; i++) + { + kSubmitDumpHistoryPrintf(&Buf, "\t--putenv %s \\\n", pszMsg); + SKIP_STR(); + } + + if (cPostArgs > 0) + { + kSubmitDumpHistoryWrite(&Buf, TUPLE("\t--post-cmd ")); + pszMsg = pszPostArgs; + cbMsg = cbPostArgs; + for (i = 0; i < cPostArgs && cbMsg > 0; i++) + { + kSubmitDumpHistoryPrintf(&Buf, " %s", pszMsg); + SKIP_STR(); + } + kSubmitDumpHistoryWrite(&Buf, TUPLE(" \\\n")); + } + kSubmitDumpHistoryWrite(&Buf, TUPLE("\t-- \\\n")); + + pszMsg = pszArgs; + cbMsg = cbArgs; + for (i = 0; i < cArgs && cbMsg > 0; i++) + { + SKIP_BYTES(1); + kSubmitDumpHistoryPrintf(&Buf, i + 1 < cArgs ? "\t%s \\\n" : "\t%s\n", pszMsg); + SKIP_STR(); + } + +#undef SKIP_BYTES +#undef SKIP_STR +#undef SKIP_STRING_ARRAY + } + + kSubmitDumpHistoryFlush(&Buf); + free(Buf.pszBuf); +} + + +/** + * Marks the worker active. + * + * On windows this involves setting up the async result read and telling + * sub_proc.c about the process. + * + * @returns Exit code. + * @param pCtx The command execution context. + * @param pWorker The worker instance to mark as active. + * @param cVerbosity The verbosity level. + * @param pChild The kmk child to associate the job with. + * @param pPidSpawned If @a *pPidSpawned is non-zero if the child is + * running, otherwise the worker is already done + * and we've returned the exit code of the job. + */ +static int kSubmitMarkActive(PKMKBUILTINCTX pCtx, PWORKERINSTANCE pWorker, int cVerbosity, struct child *pChild, pid_t *pPidSpawned) +{ +#ifdef KBUILD_OS_WINDOWS + int rc; +#endif + + pWorker->cbResultRead = 0; + +#ifdef KBUILD_OS_WINDOWS + /* + * Setup the async result read on windows. If we're slow and the worker + * very fast, this may actually get the result immediately. + */ +l_again: + rc = kSubmitReadMoreResultWin(pCtx, pWorker, "kSubmitMarkActive"); + if (rc == -1) + { +# ifndef CONFIG_NEW_WIN_CHILDREN + if (process_kmk_register_submit(pWorker->OverlappedRead.hEvent, (intptr_t)pWorker, pPidSpawned) == 0) + { /* likely */ } + else + { + /* We need to do the waiting here because sub_proc.c has too much to do. */ + warnx(pCtx, "Too many processes for sub_proc.c to handle!"); + WaitForSingleObject(pWorker->OverlappedRead.hEvent, INFINITE); + goto l_again; + } +# else + if (MkWinChildCreateSubmit((intptr_t)pWorker->OverlappedRead.hEvent, pWorker, + pWorker->pStdOut, pWorker->pStdErr, pChild, pPidSpawned) == 0) + { /* likely */ } + else + { + /* We need to do the waiting here because sub_proc.c has too much to do. */ + warnx(pCtx, "MkWinChildCreateSubmit failed!"); + WaitForSingleObject(pWorker->OverlappedRead.hEvent, INFINITE); + goto l_again; + } +# endif + } + else + { + assert(rc == 0 || pWorker->Result.s.rcExit != 0); + if (pWorker->Result.s.bWorkerExiting) + kSubmitCloseConnectOnExitingWorker(pCtx, pWorker); + if (pWorker->Result.s.rcExit && 1) + kSubmitDumpHistory(pCtx, pWorker); + *pPidSpawned = 0; + return pWorker->Result.s.rcExit; + } +#endif + + /* + * Mark it busy and move it to the active instance. + */ + pWorker->pBusyWith = pChild; +#ifndef KBUILD_OS_WINDOWS + *pPidSpawned = pWorker->pid; +#endif + + kSubmitListUnlink(&g_IdleList, pWorker); + kSubmitListAppend(&g_BusyList, pWorker); + return 0; +} + + +#ifdef KBUILD_OS_WINDOWS + +/** + * Retrieve the worker child result. + * + * If incomplete, we restart the ReadFile operation like kSubmitMarkActive does. + * + * @returns 0 on success, -1 if ReadFile was restarted. + * @param pvUser The worker instance. + * @param fBlock if we're to block waiting for the result or not. + * @param prcExit Where to return the exit code. + * @param piSigNo Where to return the signal number. + */ +int kSubmitSubProcGetResult(intptr_t pvUser, int fBlock, int *prcExit, int *piSigNo) +{ + PWORKERINSTANCE pWorker = (PWORKERINSTANCE)pvUser; + KMKBUILTINCTX FakeCtx = { "kSubmit/GetResult", NULL }; + PKMKBUILTINCTX pCtx = &FakeCtx; + + /* + * Get the overlapped result. There should be one since we're here + * because of a satisfied WaitForMultipleObject. + */ + DWORD cbRead = 0; + if (GetOverlappedResult(pWorker->hPipe, &pWorker->OverlappedRead, &cbRead, fBlock ? TRUE : FALSE)) + { + pWorker->cbResultRead += cbRead; + assert(pWorker->cbResultRead <= sizeof(pWorker->Result)); + + /* More to be read? */ + while (pWorker->cbResultRead < sizeof(pWorker->Result)) + { + int rc = kSubmitReadMoreResultWin(pCtx, pWorker, "kSubmitSubProcGetResult/more"); + if (rc == -1) + return -1; + assert(rc == 0 || pWorker->Result.s.rcExit != 0); + } + assert(pWorker->cbResultRead == sizeof(pWorker->Result)); + } + else + { + DWORD dwErr = GetLastError(); + if (dwErr == ERROR_IO_INCOMPLETE && !fBlock) + return -1; + kSubmitWinReadFailed(pCtx, pWorker, dwErr, "kSubmitSubProcGetResult/result"); + } + + /* + * Okay, we've got a result. + */ + *prcExit = pWorker->Result.s.rcExit; + switch (pWorker->Result.s.rcExit) + { + default: *piSigNo = 0; break; + case CONTROL_C_EXIT: *piSigNo = SIGINT; break; + case STATUS_INTEGER_DIVIDE_BY_ZERO: *piSigNo = SIGFPE; break; + case STATUS_ACCESS_VIOLATION: *piSigNo = SIGSEGV; break; + case STATUS_PRIVILEGED_INSTRUCTION: + case STATUS_ILLEGAL_INSTRUCTION: *piSigNo = SIGILL; break; + } + if (pWorker->Result.s.rcExit && pWorker->fDebugDumpHistoryOnFailure) + kSubmitDumpHistory(pCtx, pWorker); + if (pWorker->Result.s.bWorkerExiting) + kSubmitCloseConnectOnExitingWorker(pCtx, pWorker); + + return 0; +} + + +int kSubmitSubProcKill(intptr_t pvUser, int iSignal) +{ + return -1; +} + + +/** + * Called by process_cleanup when it's done with the worker. + * + * @param pvUser The worker instance. + */ +void kSubmitSubProcCleanup(intptr_t pvUser) +{ + PWORKERINSTANCE pWorker = (PWORKERINSTANCE)pvUser; + kSubmitListUnlink(&g_BusyList, pWorker); + kSubmitListAppend(&g_IdleList, pWorker); +} + +#endif /* KBUILD_OS_WINDOWS */ + + +/** + * atexit callback that trigger worker termination. + */ +static void kSubmitAtExitCallback(void) +{ + PWORKERINSTANCE pWorker; + DWORD msStartTick; + DWORD cKillRaids = 0; + KMKBUILTINCTX FakeCtx = { "kSubmit/atexit", NULL }; + PKMKBUILTINCTX pCtx = &FakeCtx; + + /* + * Tell all the workers to exit by breaking the connection. + */ + for (pWorker = g_IdleList.pHead; pWorker != NULL; pWorker = pWorker->pNext) + kSubmitCloseConnectOnExitingWorker(pCtx, pWorker); + for (pWorker = g_BusyList.pHead; pWorker != NULL; pWorker = pWorker->pNext) + kSubmitCloseConnectOnExitingWorker(pCtx, pWorker); + + /* + * Wait a little while for them to stop. + */ + Sleep(0); + msStartTick = GetTickCount(); + for (;;) + { + /* + * Collect handles of running processes. + */ + PWORKERINSTANCE apWorkers[MAXIMUM_WAIT_OBJECTS]; + HANDLE ahHandles[MAXIMUM_WAIT_OBJECTS]; + DWORD cHandles = 0; + + for (pWorker = g_IdleList.pHead; pWorker != NULL; pWorker = pWorker->pNext) + if (pWorker->hProcess != INVALID_HANDLE_VALUE) + { + if (cHandles < MAXIMUM_WAIT_OBJECTS) + { + apWorkers[cHandles] = pWorker; + ahHandles[cHandles] = pWorker->hProcess; + } + cHandles++; + } + for (pWorker = g_BusyList.pHead; pWorker != NULL; pWorker = pWorker->pNext) + if (pWorker->hProcess != INVALID_HANDLE_VALUE) + { + if (cHandles < MAXIMUM_WAIT_OBJECTS) + { + apWorkers[cHandles] = pWorker; + ahHandles[cHandles] = pWorker->hProcess; + } + cHandles++; + } + if (cHandles == 0) + return; + + /* + * Wait for the processes. + */ + for (;;) + { + DWORD cMsElapsed = GetTickCount() - msStartTick; + DWORD dwWait = WaitForMultipleObjects(cHandles <= MAXIMUM_WAIT_OBJECTS ? cHandles : MAXIMUM_WAIT_OBJECTS, + ahHandles, FALSE /*bWaitAll*/, + cMsElapsed < 5000 ? 5000 - cMsElapsed + 16 : 16); + if ( dwWait >= WAIT_OBJECT_0 + && dwWait <= WAIT_OBJECT_0 + MAXIMUM_WAIT_OBJECTS) + { + size_t idx = dwWait - WAIT_OBJECT_0; + CloseHandle(apWorkers[idx]->hProcess); + apWorkers[idx]->hProcess = INVALID_HANDLE_VALUE; + + if (cHandles <= MAXIMUM_WAIT_OBJECTS) + { + /* Restart the wait with the worker removed, or quit if it was the last worker. */ + cHandles--; + if (!cHandles) + return; + if (idx != cHandles) + { + apWorkers[idx] = apWorkers[cHandles]; + ahHandles[idx] = ahHandles[cHandles]; + } + continue; + } + /* else: Reconstruct the wait array so we get maximum coverage. */ + } + else if (dwWait == WAIT_TIMEOUT) + { + /* Terminate the whole bunch. */ + cKillRaids++; + if (cKillRaids == 1 && getenv("KMK_KSUBMIT_NO_KILL") == NULL) + { + warnx(pCtx, "Killing %u lingering worker processe(s)!\n", cHandles); + for (pWorker = g_IdleList.pHead; pWorker != NULL; pWorker = pWorker->pNext) + if (pWorker->hProcess != INVALID_HANDLE_VALUE) + TerminateProcess(pWorker->hProcess, WAIT_TIMEOUT); + for (pWorker = g_BusyList.pHead; pWorker != NULL; pWorker = pWorker->pNext) + if (pWorker->hProcess != INVALID_HANDLE_VALUE) + TerminateProcess(pWorker->hProcess, WAIT_TIMEOUT); + } + else + { + warnx(pCtx, "Giving up on the last %u worker processe(s). :-(\n", cHandles); + return; + } + } + else + { + /* Some kind of wait error. Could be a bad handle, check each and remove + bad ones as well as completed ones. */ + size_t idx; + warnx(pCtx, "WaitForMultipleObjects unexpectedly returned %#u (err=%u)\n", + dwWait, GetLastError()); + for (idx = 0; idx < cHandles; idx++) + { + dwWait = WaitForSingleObject(ahHandles[idx], 0 /*ms*/); + if (dwWait != WAIT_TIMEOUT) + { + CloseHandle(apWorkers[idx]->hProcess); + apWorkers[idx]->hProcess = INVALID_HANDLE_VALUE; + } + } + } + break; + } /* wait loop */ + } /* outer wait loop */ +} + + +static int kmk_builtin_kSubmit_usage(PKMKBUILTINCTX pCtx, int fIsErr) +{ + kmk_builtin_ctx_printf(pCtx, fIsErr, + "usage: %s [-Z|--zap-env] [-E|--set <var=val>] [-U|--unset <var=val>]\n" + " [-A|--append <var=val>] [-D|--prepend <var=val>]\n" + " [-s|--special-env <var=val>] [-C|--chdir <dir>]\n" + " [--wcc-brain-damage] [--no-pch-caching]\n" + " [-3|--32-bit] [-6|--64-bit] [-v] [--debug-dump-history]\n" + " [-P|--post-cmd <cmd> [args]] -- <program> [args]\n" + " or: %s --help\n" + " or: %s --version\n" + "\n" + "Options:\n" + " -Z, --zap-env, -i, --ignore-environment\n" + " Zaps the environment. Position dependent.\n" + " -E, --set <var>=[value]\n" + " Sets an environment variable putenv fashion. Position dependent.\n" + " -U, --unset <var>\n" + " Removes an environment variable. Position dependent.\n" + " -A, --append <var>=<value>\n" + " Appends the given value to the environment variable.\n" + " -D,--prepend <var>=<value>\n" + " Prepends the given value to the environment variable.\n" + " -s,--special-env <var>=<value>\n" + " Same as --set, but flags the variable for further expansion\n" + " within kWorker. Replacements:\n" + " @@PROCESSOR_GROUP@@ - The processor group number.\n" + " @@AUTHENTICATION_ID@@ - The authentication ID from the process token.\n" + " @@PID@@ - The kWorker process ID.\n" + " @@@@ - Escaped \"@@\".\n" + " @@DEBUG_COUNTER@@ - An ever increasing counter (starts at zero).\n" + " -C, --chdir <dir>\n" + " Specifies the current directory for the program. Relative paths\n" + " are relative to the previous -C option. Default is getcwd value.\n" + " -3, --32-bit\n" + " Selects a 32-bit kWorker process. Default: kmk bit count\n" + " -6, --64-bit\n" + " Selects a 64-bit kWorker process. Default: kmk bit count\n" + " --wcc-brain-damage\n" + " Works around wcc and wcc386 (Open Watcom) not following normal\n" + " quoting conventions on Windows, OS/2, and DOS.\n" + " --no-pch-caching\n" + " Do not cache precompiled header files because they're being created.\n" + " -v,--verbose\n" + " More verbose execution.\n" + " --debug-dump-history\n" + " Dump the history as of the submitted command. Handy for debugging\n" + " trouble caused by a previous job.\n" + " --debug-dump-history-on-failure, --no-debug-dump-history-on-failure\n" + " Dump the history on failure. Can also be enabled by a non-empty\n" + " KMK_KSUBMIT_DUMP_HISTORY_ON_FAILURE variable (first invocation only).\n" + " -P|--post-cmd <cmd> ...\n" + " For running a built-in command on the output, specifying the command\n" + " and all it's parameters. Currently supported commands:\n" + " kDepObj\n" + " -V,--version\n" + " Show the version number.\n" + " -h,--help\n" + " Show this usage information.\n" + "\n" + , + pCtx->pszProgName, pCtx->pszProgName, pCtx->pszProgName); + return 2; +} + + +int kmk_builtin_kSubmit(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx, struct child *pChild, pid_t *pPidSpawned) +{ +#ifdef KBUILD_OS_WINDOWS + static int s_fInitialized = 0; +#endif + int rcExit = 0; + int iArg; + unsigned cAllocatedEnvVars; + unsigned cEnvVars; + char **papszEnvVars; + const char *pszExecutable = NULL; + const char *pszSpecialEnv = NULL; + int iPostCmd = argc; + int cPostCmdArgs = 0; + unsigned cBitsWorker = g_cArchBits; + int fWatcomBrainDamage = 0; + int fNoPchCaching = 0; + int fDebugDumpHistory = 0; + static int s_fDebugDumpHistoryOnFailure = -1; + int fDebugDumpHistoryOnFailure = s_fDebugDumpHistoryOnFailure; + int cVerbosity = 0; + size_t const cbCwdBuf = GET_PATH_MAX; + PATH_VAR(szCwd); + +#ifdef KBUILD_OS_WINDOWS + /* + * First time thru we must perform some initializations. + */ + if (s_fInitialized) + { } + else + { + MkWinChildInitCpuGroupAllocator(&g_SubmitProcessorGroupAllocator); +# if K_ARCH_BITS == 64 + MkWinChildInitCpuGroupAllocator(&g_SubmitProcessorGroupAllocator32); +# endif + *(FARPROC *)&g_pfnSetThreadGroupAffinity = GetProcAddress(GetModuleHandleW(L"KERNEL32.DLL"), "SetThreadGroupAffinity"); + s_fInitialized = 1; + } +#endif + if (fDebugDumpHistoryOnFailure != -1) + { /* likely */ } + else + { + struct variable *pVar = lookup_variable(TUPLE("KMK_KSUBMIT_DUMP_HISTORY_ON_FAILURE")); + fDebugDumpHistoryOnFailure = pVar && *pVar->value != '\0'; + s_fDebugDumpHistoryOnFailure = fDebugDumpHistoryOnFailure; + } + + /* + * Create default program environment. + * + * Note! We only clean up the environment on successful return, assuming + * make will stop after that. + */ + if (getcwd_fs(szCwd, cbCwdBuf) != NULL) + { /* likely */ } + else + return err(pCtx, 1, "getcwd_fs failed\n"); + + /* The environment starts out in read-only mode and will be duplicated if modified. */ + cAllocatedEnvVars = 0; + papszEnvVars = envp; + cEnvVars = 0; + while (papszEnvVars[cEnvVars] != NULL) + cEnvVars++; + + /* + * Parse the command line. + */ + for (iArg = 1; iArg < argc; iArg++) + { + const char *pszArg = argv[iArg]; + if (*pszArg == '-') + { + char chOpt = *++pszArg; + pszArg++; + if (chOpt != '-') + { + if (chOpt != '\0') + { /* likely */ } + else + { + errx(pCtx, 1, "Incomplete option: '-'"); + return kmk_builtin_kSubmit_usage(pCtx, 1); + } + } + else + { + /* '--' indicates where the bits to execute start. */ + if (*pszArg == '\0') + { + iArg++; + break; + } + + if ( strcmp(pszArg, "wcc-brain-damage") == 0 + || strcmp(pszArg, "watcom-brain-damage") == 0) + { + fWatcomBrainDamage = 1; + continue; + } + + if (strcmp(pszArg, "no-pch-caching") == 0) + { + fNoPchCaching = 1; + continue; + } + + if (strcmp(pszArg, "debug-dump-history") == 0) + { + fDebugDumpHistory = 1; + continue; + } + + if (strcmp(pszArg, "debug-dump-history-on-failure") == 0) + { + fDebugDumpHistoryOnFailure = 1; + continue; + } + + if (strcmp(pszArg, "no-debug-dump-history-on-failure") == 0) + { + fDebugDumpHistoryOnFailure = 0; + continue; + } + + /* convert to short. */ + if (strcmp(pszArg, "help") == 0) + chOpt = 'h'; + else if (strcmp(pszArg, "version") == 0) + chOpt = 'V'; + else if (strcmp(pszArg, "set") == 0) + chOpt = 'E'; + else if (strcmp(pszArg, "append") == 0) + chOpt = 'A'; + else if (strcmp(pszArg, "prepend") == 0) + chOpt = 'D'; + else if (strcmp(pszArg, "unset") == 0) + chOpt = 'U'; + else if ( strcmp(pszArg, "zap-env") == 0 + || strcmp(pszArg, "ignore-environment") == 0 /* GNU env compatibility. */ ) + chOpt = 'Z'; + else if (strcmp(pszArg, "chdir") == 0) + chOpt = 'C'; + else if (strcmp(pszArg, "set-special") == 0) + chOpt = 's'; + else if (strcmp(pszArg, "post-cmd") == 0) + chOpt = 'P'; + else if (strcmp(pszArg, "32-bit") == 0) + chOpt = '3'; + else if (strcmp(pszArg, "64-bit") == 0) + chOpt = '6'; + else if (strcmp(pszArg, "verbose") == 0) + chOpt = 'v'; + else if (strcmp(pszArg, "executable") == 0) + chOpt = 'e'; + else + { + errx(pCtx, 2, "Unknown option: '%s'", pszArg - 2); + return kmk_builtin_kSubmit_usage(pCtx, 1); + } + pszArg = ""; + } + + do + { + /* Get option value first, if the option takes one. */ + const char *pszValue = NULL; + switch (chOpt) + { + case 'A': + case 'C': + case 'E': + case 'U': + case 'D': + case 'e': + case 's': + if (*pszArg != '\0') + pszValue = pszArg + (*pszArg == ':' || *pszArg == '='); + else if (++iArg < argc) + pszValue = argv[iArg]; + else + { + errx(pCtx, 1, "Option -%c requires a value!", chOpt); + return kmk_builtin_kSubmit_usage(pCtx, 1); + } + break; + } + + switch (chOpt) + { + case 'Z': + case 'i': /* GNU env compatibility. */ + rcExit = kBuiltinOptEnvZap(pCtx, &papszEnvVars, &cEnvVars, &cAllocatedEnvVars, cVerbosity); + if (rcExit == 0) + break; + return rcExit; + + case 'E': + rcExit = kBuiltinOptEnvSet(pCtx, &papszEnvVars, &cEnvVars, &cAllocatedEnvVars, cVerbosity, pszValue); + if (rcExit == 0) + break; + return rcExit; + + case 'A': + rcExit = kBuiltinOptEnvAppend(pCtx, &papszEnvVars, &cEnvVars, &cAllocatedEnvVars, cVerbosity, pszValue); + if (rcExit == 0) + break; + return rcExit; + + case 'D': + rcExit = kBuiltinOptEnvPrepend(pCtx, &papszEnvVars, &cEnvVars, &cAllocatedEnvVars, cVerbosity, pszValue); + if (rcExit == 0) + break; + return rcExit; + + case 'U': + rcExit = kBuiltinOptEnvUnset(pCtx, &papszEnvVars, &cEnvVars, &cAllocatedEnvVars, cVerbosity, pszValue); + if (rcExit == 0) + break; + return rcExit; + + case 'C': + rcExit = kBuiltinOptChDir(pCtx, szCwd, cbCwdBuf, pszValue); + if (rcExit == 0) + break; + return rcExit; + + case 's': + if (pszSpecialEnv) + return errx(pCtx, 1, "The -s option can only be used once!"); + pszSpecialEnv = pszValue; + rcExit = kBuiltinOptEnvSet(pCtx, &papszEnvVars, &cEnvVars, &cAllocatedEnvVars, cVerbosity, pszValue); + if (rcExit == 0) + break; + return rcExit; + + case 'P': + if (cPostCmdArgs > 0) + return errx(pCtx, 1, "The -P option can only be used once!"); + if (*pszArg != '\0') + return errx(pCtx, 1, "The cmd part of the -P needs to be a separate argument!"); + iPostCmd = ++iArg; + if (iArg >= argc) + return errx(pCtx, 1, "The -P option requires a command following it!"); + while (iArg < argc && strcmp(argv[iArg], "--") != 0) + iArg++; + cPostCmdArgs = iArg - iPostCmd; + iArg--; + break; + + case '3': + cBitsWorker = 32; + break; + + case '6': + cBitsWorker = 64; + break; + + case 'e': + pszExecutable = pszValue; + break; + + case 'v': + cVerbosity++; + break; + + case 'h': + kmk_builtin_kSubmit_usage(pCtx, 0); + kBuiltinOptEnvCleanup(&papszEnvVars, cEnvVars, &cAllocatedEnvVars); + return 0; + + case 'V': + kBuiltinOptEnvCleanup(&papszEnvVars, cEnvVars, &cAllocatedEnvVars); + return kbuild_version(argv[0]); + } + } while ((chOpt = *pszArg++) != '\0'); + } + else + { + errx(pCtx, 1, "Unknown argument: '%s'", pszArg); + return kmk_builtin_kSubmit_usage(pCtx, 1); + } + } + + /* + * Check that we've got something to execute. + */ + if (iArg < argc) + { + uint32_t cbMsg; + void *pvMsg = kSubmitComposeJobMessage(pszExecutable, &argv[iArg], papszEnvVars, szCwd, + fWatcomBrainDamage, fNoPchCaching, pszSpecialEnv, + &argv[iPostCmd], cPostCmdArgs, &cbMsg); + PWORKERINSTANCE pWorker = kSubmitSelectWorkSpawnNewIfNecessary(pCtx, cBitsWorker, cVerbosity); + if (pWorker) + { + /* Before we send off the job, we should dump pending output, since + the kWorker process currently does not coordinate its output with + the output.c mechanics. */ +#ifdef CONFIG_NEW_WIN_CHILDREN + if (pCtx->pOut && !pWorker->pStdOut) +#else + if (pCtx->pOut) +#endif + output_dump(pCtx->pOut); + pWorker->fDebugDumpHistoryOnFailure = fDebugDumpHistoryOnFailure; + rcExit = kSubmitSendJobMessage(pCtx, pWorker, pvMsg, cbMsg, 0 /*fNoRespawning*/, cVerbosity); + if (rcExit == 0) + { + pvMsg = kSubmitUpdateHistory(pWorker, pvMsg, cbMsg); + if (fDebugDumpHistory) + kSubmitDumpHistory(pCtx, pWorker); + rcExit = kSubmitMarkActive(pCtx, pWorker, cVerbosity, pChild, pPidSpawned); + } + + if (!g_fAtExitRegistered) + if (atexit(kSubmitAtExitCallback) == 0) + g_fAtExitRegistered = 1; + } + else + rcExit = 1; + free(pvMsg); + } + else + { + errx(pCtx, 1, "Nothing to executed!"); + rcExit = kmk_builtin_kSubmit_usage(pCtx, 1); + } + + kBuiltinOptEnvCleanup(&papszEnvVars, cEnvVars, &cAllocatedEnvVars); + return rcExit; +} + |