// ---------------------------------------------------------------------------- // // Program: RunAll // // Purpose: Run all processes simultaneously with optional fatal error // on any process failure or timeout. This is intended to be // used by the automated build process to wrapper running // bootd and the kernel debugger side-by-side. // // Copyright (c) Microsoft Corporation. All Rights Reserved. // // ---------------------------------------------------------------------------- #define _WIN32_WINNT 0x0500 #include #include #include #include #include #include static BOOL g_bVerbose = FALSE; #define tprintf if (g_bVerbose) printf static BOOL WINAPI CtrlHandler(DWORD dwCtrlType) { printf("Caught control event %I32u", dwCtrlType); switch (dwCtrlType) { case CTRL_C_EVENT: case CTRL_BREAK_EVENT: GenerateConsoleCtrlEvent(dwCtrlType, 0); default: break; } ExitProcess((UINT)-2); } static void ErrorReport(char *function) { VOID *message; DWORD error = GetLastError(); FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &message, 0, NULL ); printf("runall: %s failed with error %d: %s", function, error, message); LocalFree(message); } void usage() { printf("Usage: runall [options] cmd [cmd1] ...\n\n"); printf("Options:\n"); printf(" /L:logdir - Write output from processes to logdir (must exist).\n"); printf(" /T:seconds - Set timeout in seconds.\n"); printf("\nRuns specified commands in parallel and wait for all to terminate successfully.\n\nThe program returns -1 if any process fails to launch, or exits with a non zero error-code, or the option timeout expires."); } static BOOL ParseInt64(LPCSTR lpszValue, PINT64 pValue) { PCHAR pszStop; errno = 0; *pValue = _strtoi64(lpszValue, &pszStop, 10); // Return no overflow and all characters parsed. return errno == 0 && *pszStop == '\0'; } static HANDLE OpenLogFile(LPCSTR lpszLogDir, int fileNumber) { HANDLE hLogFile; CHAR lpszLogPath[_MAX_PATH]; _snprintf_s(lpszLogPath, sizeof(lpszLogPath), _TRUNCATE, "%s\\%d.log", lpszLogDir, fileNumber); lpszLogPath[_MAX_PATH - 1] = '0'; hLogFile = CreateFile(lpszLogPath, GENERIC_WRITE | GENERIC_READ, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (INVALID_HANDLE_VALUE == hLogFile) { ErrorReport("CreateFile (for log)"); } return hLogFile; } static int RunAndWait(int nProcesses, LPPROCESS_INFORMATION lpProcessInfo, LPCSTR lpszCommandLine[], time_t endTime) { HANDLE hProcesses[MAXIMUM_WAIT_OBJECTS]; int nProcessOrdinal[MAXIMUM_WAIT_OBJECTS]; int i; for (i = 0; i < nProcesses; i++) { if (ResumeThread(lpProcessInfo[i].hThread) == -1) { ErrorReport("ResumeThread"); return -1; } hProcesses[i] = lpProcessInfo[i].hProcess; nProcessOrdinal[i] = i; } while (nProcesses > 0) { DWORD dwMilliseconds; DWORD dwResult; time_t t; time(&t); if (t >= endTime) { printf("runall: timeout\n"); return -1; } t = (endTime - t) * 1000; // Note INFINITE - 1 so we don't sleep forever. dwMilliseconds = (DWORD)((t < INFINITE) ? t : INFINITE - 1); dwResult = WaitForMultipleObjects(nProcesses, hProcesses, FALSE, dwMilliseconds); tprintf("Next timeout = %d ms\n", dwMilliseconds); if (dwResult == WAIT_TIMEOUT) { tprintf("runall: WAIT_TIMEOUT\n"); } else if (dwResult >= WAIT_ABANDONED_0 && dwResult <= WAIT_ABANDONED_0 + nProcesses) { printf("runall: wait abandoned (process %d)\n", dwResult - WAIT_ABANDONED_0); return -1; } else { DWORD dwProcess = dwResult - WAIT_OBJECT_0; DWORD dwExitCode; hProcesses[dwProcess] = hProcesses[--nProcesses]; nProcessOrdinal[dwProcess] = nProcessOrdinal[nProcesses]; if (GetExitCodeProcess(hProcesses[dwProcess], &dwExitCode) == 0) { ErrorReport("GetExitCodeProcess"); return -1; } printf("runall: Command \"%s\" => exited %d\n", lpszCommandLine[nProcessOrdinal[dwProcess]], dwExitCode); if (dwExitCode != 0) { return -1; } } } return 0; } static int RunAll(int nProcesses, LPCSTR lpszCommandLine[], LPCSTR logDir, time_t endTime) { PROCESS_INFORMATION pi[MAXIMUM_WAIT_OBJECTS]; STARTUPINFO si; int exitCode = -1; int i; for (i = 0; i < nProcesses; i++) { DWORD dwCreationFlags = CREATE_SUSPENDED | CREATE_NO_WINDOW; ZeroMemory(&si, sizeof(si)); si.cb = sizeof(si); if (NULL != logDir) { si.dwFlags = STARTF_USESTDHANDLES; si.hStdOutput = OpenLogFile(logDir, i); if (INVALID_HANDLE_VALUE == si.hStdOutput) { break; } SetHandleInformation(si.hStdOutput, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT); si.hStdError = si.hStdOutput; } if (!CreateProcess(NULL, (LPSTR)lpszCommandLine[i], NULL, NULL, TRUE, dwCreationFlags, NULL, NULL, &si, &pi[i])) { ErrorReport("CreateProcess"); break; } tprintf("runall: Created PID %u: %s\n", pi[i].dwProcessId, lpszCommandLine[i]); } if (i == nProcesses) { exitCode = RunAndWait(nProcesses, pi, lpszCommandLine, endTime); } while (--i >= 0) { if (TerminateProcess(pi[i].hProcess, -1)) { tprintf("runall: Terminated PID %u: %s\n", pi[i].dwProcessId, lpszCommandLine[i]); } } return exitCode; } static double FileTimeToSeconds(FILETIME ftValue) { PINT64 pV = (PINT64)&ftValue; double d = (*pV) * 1e-7; return d; } static void DisplayProcessTimes() { FILETIME ftCreation; FILETIME ftExitTime; FILETIME ftKernelTime; FILETIME ftUserTime; GetProcessTimes((HANDLE)-1, &ftCreation, &ftExitTime, &ftKernelTime, &ftUserTime); printf("runall: Process Time Information\n"); printf("runall: Kernel time: %.2f\n", FileTimeToSeconds(ftKernelTime)); printf("runall: User time: %.2f\n", FileTimeToSeconds(ftUserTime)); } int main(int argc, LPCSTR argv[]) { BOOL bAccounting = FALSE; BOOL bLaunchFailure = FALSE; time_t endTime = _I64_MAX; INT64 timeout = 0; LPCSTR lpszLogDir = NULL; INT32 exitCode; argc--; argv++; while (argc != 0 && (argv[0][0] == '/' || argv[0][0] == '-')) { switch (tolower(argv[0][1])) { case 'a': bAccounting = TRUE; break; case 't': if (argv[0][2] == ':' && ParseInt64(&argv[0][3], &timeout)) { time(&endTime); endTime += (time_t)timeout; } else { printf("Bad timeout argument."); return -1; } break; case 'l': if (argv[0][2] == ':') { lpszLogDir = argv[0] + 3; } else { printf("Bad log dir argument."); return -1; } break; case 'v': g_bVerbose = TRUE; break; default: usage(); return -1; } argc --; argv ++; } if (argc == 0) { usage(); return -1; } else if (argc > MAXIMUM_WAIT_OBJECTS) { printf("Supported process limit exceeded."); return -1; } SetConsoleCtrlHandler(CtrlHandler, TRUE); exitCode = RunAll(argc, argv, lpszLogDir, endTime); if (bAccounting) { DisplayProcessTimes(); } return exitCode; }