/* $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 * * 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 * */ /******************************************************************************* * 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 #include #include #include #include #ifdef HAVE_ALLOCA_H # include #endif #if defined(_MSC_VER) # include # include # include # include #else # include #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 ] [-U|--unset ]\n" " [-A|--append ] [-D|--prepend ]\n" " [-s|--special-env ] [-C|--chdir ]\n" " [--wcc-brain-damage] [--no-pch-caching]\n" " [-3|--32-bit] [-6|--64-bit] [-v] [--debug-dump-history]\n" " [-P|--post-cmd [args]] -- [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 =[value]\n" " Sets an environment variable putenv fashion. Position dependent.\n" " -U, --unset \n" " Removes an environment variable. Position dependent.\n" " -A, --append =\n" " Appends the given value to the environment variable.\n" " -D,--prepend =\n" " Prepends the given value to the environment variable.\n" " -s,--special-env =\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 \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 ...\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; }