791 lines
24 KiB
C
791 lines
24 KiB
C
// ----------------------------------------------------------------------------
|
|
//
|
|
// Program: JobControl
|
|
//
|
|
// Purpose: Run multiple processes within a job object and/or manually kill
|
|
// existing job objects. See usage for more details.
|
|
//
|
|
// (C) Microsoft Corporation
|
|
//
|
|
// ----------------------------------------------------------------------------
|
|
|
|
#define _WIN32_WINNT 0x0500
|
|
|
|
#define YESWINDOWSTATION
|
|
#define YESSECURITY
|
|
|
|
#include <winlean.h>
|
|
|
|
#include <assert.h>
|
|
#include <limits.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
|
|
typedef struct _CONTROLLED_PROCESS {
|
|
HANDLE Thread;
|
|
HANDLE Process;
|
|
DWORD ProcessId;
|
|
BOOL ErrorExitFatal;
|
|
} CONTROLLED_PROCESS, *PCONTROLLED_PROCESS;
|
|
|
|
typedef struct _CONTROLLED_PROCESS_SET {
|
|
DWORD ProcessCount;
|
|
DWORD MaxProcessCount;
|
|
PCONTROLLED_PROCESS Processes;
|
|
} CONTROLLED_PROCESS_SET, *PCONTROLLED_PROCESS_SET;
|
|
|
|
typedef struct _MONITOR_THREAD_ARGS {
|
|
HANDLE hJob;
|
|
PCONTROLLED_PROCESS_SET pSet;
|
|
DWORD dwTimeoutSeconds;
|
|
HANDLE hMonitoringStarted;
|
|
} MONITOR_THREAD_ARGS, *PMONITOR_THREAD_ARGS;
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
static const DWORD dwCompletionKey = 2;
|
|
|
|
// Global job handle needed for graceful shutdown by control
|
|
// event handler.
|
|
static HANDLE ghJob = NULL;
|
|
|
|
static BOOL gbVerbose = FALSE;
|
|
|
|
#define tprintf(x, ...) \
|
|
do { \
|
|
if (gbVerbose) printf(x, __VA_ARGS__); \
|
|
} while (FALSE)
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
static BOOL WINAPI CtrlHandler(DWORD dwCtrlType)
|
|
{
|
|
tprintf("Caught control event %I32u", dwCtrlType);
|
|
switch (dwCtrlType) {
|
|
case CTRL_C_EVENT:
|
|
case CTRL_BREAK_EVENT:
|
|
GenerateConsoleCtrlEvent(dwCtrlType, 0);
|
|
default:
|
|
break;
|
|
}
|
|
if (ghJob != NULL) {
|
|
TerminateJobObject(ghJob, (UINT)-1);
|
|
}
|
|
ExitProcess((UINT)-2);
|
|
}
|
|
|
|
static void Usage()
|
|
{
|
|
printf(
|
|
"Usage:\n" \
|
|
"jobcontrol <command> <jobname> [options]\n" \
|
|
"\n" \
|
|
"where command is one of create, kill, query.\n" \
|
|
"\n" \
|
|
"CREATE COMMAND\n" \
|
|
"--------------\n" \
|
|
"\n" \
|
|
"The \"create\" command creates a job object and runs any number\n" \
|
|
"of commands with it. The command runs for as long as the\n" \
|
|
"specified commands are executing.\n" \
|
|
"\n" \
|
|
"The options available are:\n" \
|
|
" /LimitJobTime <seconds> - Set limit on elapsed job time.\n" \
|
|
" /LimitJobUserTime <seconds> - Set limit on elapsed job user mode time.\n" \
|
|
" /LimitProcessUserTime <seconds> - Set limit on per-process user mode time\n" \
|
|
" /B - Print basic accounting information.\n" \
|
|
" /C <command> - Run command in job.\n" \
|
|
" /T <command> - Run command in job and terminate if\n" \
|
|
" the command fails (exit code != 0).\n" \
|
|
" /V - Run verbosely.\n" \
|
|
"\n" \
|
|
"Example:\n" \
|
|
" jobcontrol create MyJob /LimitJobTime 10 /C \"cmd.exe\"\n" \
|
|
"\n" \
|
|
"KILL COMMAND\n" \
|
|
"------------\n" \
|
|
"\n" \
|
|
"The \"kill\" command terminates the processes within a job\n" \
|
|
"object. There are no relevant options.\n" \
|
|
"\n" \
|
|
"Example:\n" \
|
|
" jobcontrol kill MyJob\n" \
|
|
"\n"
|
|
"QUERY COMMAND\n" \
|
|
"-------------\n" \
|
|
"\n" \
|
|
"Determine if process is within a job already, exit 1 if so, 0 otherwise."
|
|
);
|
|
}
|
|
|
|
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("jobcontrol: %s failed with error %d: %s",
|
|
function, error, message);
|
|
|
|
LocalFree(message);
|
|
}
|
|
|
|
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 BOOL
|
|
ParseUInt32(LPCSTR lpszValue, PUINT32 pValue)
|
|
{
|
|
PCHAR pszStop;
|
|
|
|
errno = 0;
|
|
*pValue = strtoul(lpszValue, &pszStop, 10);
|
|
|
|
// Return no overflow and all characters parsed.
|
|
return errno == 0 && *pszStop == '\0';
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Interactivity check
|
|
|
|
static BOOL IsInteractive()
|
|
{
|
|
static int isInteractive = -1;
|
|
|
|
if (isInteractive < 0) {
|
|
HANDLE hwinsta;
|
|
if (NULL != (hwinsta = GetProcessWindowStation())) {
|
|
USEROBJECTFLAGS uof;
|
|
DWORD dwLengthNeeded;
|
|
if (GetUserObjectInformation(hwinsta, UOI_FLAGS, &uof,
|
|
sizeof(uof), &dwLengthNeeded) == 0) {
|
|
ErrorReport("GetUserObjectInformation");
|
|
ExitProcess((DWORD)-1);
|
|
}
|
|
isInteractive = (uof.dwFlags & WSF_VISIBLE) ? 1 : 0;
|
|
tprintf("Process is interactive: %c\n",
|
|
isInteractive ? 't' : 'f');
|
|
}
|
|
else {
|
|
ErrorReport("GetProcessWindowStation");
|
|
ExitProcess((DWORD)-1);
|
|
}
|
|
}
|
|
return isInteractive ? TRUE : FALSE;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Process Set helpers
|
|
|
|
static PCONTROLLED_PROCESS_SET
|
|
AllocateProcessSet(DWORD dwMaxProcesses)
|
|
{
|
|
size_t cbBytes = (sizeof(CONTROLLED_PROCESS_SET) +
|
|
dwMaxProcesses * sizeof(CONTROLLED_PROCESS));
|
|
PCONTROLLED_PROCESS_SET pSet = (PCONTROLLED_PROCESS_SET)malloc(cbBytes);
|
|
if (NULL != pSet) {
|
|
pSet->ProcessCount = 0;
|
|
pSet->MaxProcessCount = dwMaxProcesses;
|
|
pSet->Processes = (PCONTROLLED_PROCESS)(pSet + 1);
|
|
}
|
|
return pSet;
|
|
}
|
|
|
|
static void
|
|
FreeProcessSet(PCONTROLLED_PROCESS_SET pSet)
|
|
{
|
|
free(pSet);
|
|
pSet = NULL;
|
|
}
|
|
|
|
static PCONTROLLED_PROCESS
|
|
FindProcessInSet(PCONTROLLED_PROCESS_SET pSet, DWORD dwProcessId)
|
|
{
|
|
DWORD i;
|
|
for (i = 0; i < pSet->ProcessCount; i++) {
|
|
if (pSet->Processes[i].ProcessId == dwProcessId) {
|
|
return &pSet->Processes[i];
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
AddProcessToSet(PCONTROLLED_PROCESS_SET pSet,
|
|
HANDLE hThread,
|
|
HANDLE hProcess,
|
|
DWORD dwProcessId,
|
|
BOOL bErrorExitFatal)
|
|
{
|
|
PCONTROLLED_PROCESS pProcess;
|
|
|
|
assert(FindProcessInSet(pSet, dwProcessId) == NULL);
|
|
assert(pSet->ProcessCount < pSet->MaxProcessCount);
|
|
|
|
pProcess = &pSet->Processes[pSet->ProcessCount];
|
|
pProcess->Thread = hThread;
|
|
pProcess->Process = hProcess;
|
|
pProcess->ProcessId = dwProcessId;
|
|
pProcess->ErrorExitFatal = bErrorExitFatal;
|
|
pSet->ProcessCount++;
|
|
}
|
|
|
|
static BOOL
|
|
RemoveProcessFromSet(PCONTROLLED_PROCESS_SET pSet, DWORD dwProcessId)
|
|
{
|
|
DWORD i;
|
|
for (i = 0; i < pSet->ProcessCount; i++) {
|
|
if (pSet->Processes[i].ProcessId == dwProcessId) {
|
|
tprintf("Removing process %u from process set\n", dwProcessId);
|
|
if (pSet->ProcessCount > 1) {
|
|
// Swap with last element so it's there for inspection
|
|
CONTROLLED_PROCESS tmp = pSet->Processes[i];
|
|
pSet->Processes[i] = pSet->Processes[pSet->ProcessCount - 1];
|
|
pSet->Processes[pSet->ProcessCount - 1] = tmp;
|
|
}
|
|
pSet->ProcessCount--;
|
|
return TRUE;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
static BOOL
|
|
LimitJobUserTime(HANDLE hJob, DWORD dwLimitFlags, LPCSTR lpszValue)
|
|
{
|
|
JOBOBJECT_BASIC_LIMIT_INFORMATION limits;
|
|
__int64 timeout;
|
|
|
|
ZeroMemory(&limits, sizeof(limits));
|
|
|
|
if (ParseInt64(lpszValue, &timeout) == FALSE) {
|
|
fprintf(stdout, "Bad user time limit value.");
|
|
return FALSE;
|
|
}
|
|
tprintf("Limiting job time to %I64d seconds\n", timeout);
|
|
timeout *= 1000 * 1000 * 10; // seconds to W32 kernel ticks
|
|
tprintf("Limiting job time to %I64d ticks\n", timeout);
|
|
|
|
if (dwLimitFlags == JOB_OBJECT_LIMIT_JOB_TIME) {
|
|
CopyMemory(&limits.PerJobUserTimeLimit, &timeout, sizeof(timeout));
|
|
}
|
|
else if (dwLimitFlags == JOB_OBJECT_LIMIT_PROCESS_TIME) {
|
|
CopyMemory(&limits.PerProcessUserTimeLimit, &timeout, sizeof(timeout));
|
|
}
|
|
else {
|
|
fprintf(stdout, "Bad time limit target.\n");
|
|
return FALSE;
|
|
}
|
|
limits.LimitFlags = dwLimitFlags;
|
|
|
|
if (SetInformationJobObject(hJob, JobObjectBasicLimitInformation,
|
|
&limits, sizeof(limits)) == 0) {
|
|
ErrorReport("SetInformationJobObject");
|
|
return FALSE;
|
|
}
|
|
ErrorReport("SetInformationJobObject (no error)");
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static BOOL
|
|
AddProcessToJob(HANDLE hJob,
|
|
LPCSTR lpCommandLine,
|
|
PCONTROLLED_PROCESS_SET pSet,
|
|
DWORD bErrorExitFatal)
|
|
{
|
|
PROCESS_INFORMATION pi;
|
|
STARTUPINFO si;
|
|
DWORD dwCreationFlags;
|
|
|
|
ZeroMemory(&si, sizeof(si));
|
|
si.cb = sizeof(si);
|
|
|
|
dwCreationFlags = CREATE_SUSPENDED /* | CREATE_BREAKAWAY_FROM_JOB */;
|
|
if (!IsInteractive()) {
|
|
// If non-interactive we are presumably being run by a
|
|
// process and do not want to interact with the desktop.
|
|
// If we attempt to create a window, CreateProcess will
|
|
// die with permission denied.
|
|
dwCreationFlags |= CREATE_NO_WINDOW;
|
|
}
|
|
|
|
if (!CreateProcess(NULL, (LPSTR)lpCommandLine, NULL, NULL, FALSE,
|
|
dwCreationFlags, NULL, NULL, &si, &pi)) {
|
|
fprintf(stdout, "Failed to create process: \"%s\"\n", lpCommandLine);
|
|
ErrorReport("CreateProcess");
|
|
return FALSE;
|
|
}
|
|
else if (!AssignProcessToJobObject(hJob, pi.hProcess)) {
|
|
ErrorReport("AssignProcessToJobObject");
|
|
TerminateProcess(pi.hProcess, (UINT)-1);
|
|
return FALSE;
|
|
}
|
|
|
|
AddProcessToSet(pSet, pi.hThread, pi.hProcess, pi.dwProcessId,
|
|
bErrorExitFatal);
|
|
|
|
tprintf("jobcontrol: Adding process (%u): %s\n",
|
|
pi.dwProcessId, lpCommandLine);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static BOOL
|
|
CreateAndAssociateCompletionPort(HANDLE hJob, PHANDLE phIoPort)
|
|
{
|
|
JOBOBJECT_ASSOCIATE_COMPLETION_PORT jacp;
|
|
HANDLE hIoPort;
|
|
DWORD dwKey;
|
|
|
|
*phIoPort = INVALID_HANDLE_VALUE;
|
|
|
|
if (NULL == (hIoPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE,
|
|
NULL, dwCompletionKey, 1))) {
|
|
ErrorReport("CreateIoCompletionPort");
|
|
return FALSE;
|
|
}
|
|
|
|
dwKey = dwCompletionKey;
|
|
jacp.CompletionKey = (PVOID)dwKey;
|
|
jacp.CompletionPort = hIoPort;
|
|
if (!SetInformationJobObject(hJob,
|
|
JobObjectAssociateCompletionPortInformation,
|
|
&jacp, sizeof(jacp))) {
|
|
ErrorReport("SetInformationJobObject");
|
|
CloseHandle(hIoPort);
|
|
return FALSE;
|
|
}
|
|
*phIoPort = hIoPort;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static DWORD WINAPI MonitorJobThreadProc(LPVOID lpParams)
|
|
{
|
|
PMONITOR_THREAD_ARGS pJobArgs = (PMONITOR_THREAD_ARGS)lpParams;
|
|
HANDLE hIoPort = NULL;
|
|
BOOL bStop = FALSE;
|
|
BOOL bFatalError = FALSE;
|
|
|
|
time_t nowTime = 0i64;
|
|
time_t endTime = _I64_MAX;
|
|
|
|
if (!CreateAndAssociateCompletionPort(pJobArgs->hJob, &hIoPort)) {
|
|
ExitThread((DWORD)-1);
|
|
}
|
|
|
|
if (pJobArgs->dwTimeoutSeconds > 0) {
|
|
time(&nowTime);
|
|
endTime = nowTime + pJobArgs->dwTimeoutSeconds;
|
|
}
|
|
|
|
tprintf("Watching job (supplied = %u, %I64d, %I64d, timeout = %I64d)\n",
|
|
pJobArgs->dwTimeoutSeconds, endTime, nowTime, endTime - nowTime);
|
|
|
|
SetEvent(pJobArgs->hMonitoringStarted);
|
|
|
|
while (time(&nowTime) < endTime && !bStop) {
|
|
DWORD dwBytes = 0;
|
|
ULONG_PTR key = 0;
|
|
LPOVERLAPPED pOverlapped = NULL;
|
|
DWORD waitMillis = INFINITE;
|
|
|
|
if (endTime - nowTime < INFINITE * 1000i64) {
|
|
waitMillis = (DWORD)((endTime - nowTime) * 1000);
|
|
}
|
|
|
|
if (!GetQueuedCompletionStatus(hIoPort, &dwBytes, &key,
|
|
&pOverlapped, waitMillis)) {
|
|
continue;
|
|
}
|
|
assert (key == dwCompletionKey);
|
|
|
|
switch (dwBytes) {
|
|
case JOB_OBJECT_MSG_END_OF_JOB_TIME:
|
|
case JOB_OBJECT_MSG_END_OF_PROCESS_TIME:
|
|
case JOB_OBJECT_MSG_ACTIVE_PROCESS_LIMIT:
|
|
case JOB_OBJECT_MSG_PROCESS_MEMORY_LIMIT:
|
|
case JOB_OBJECT_MSG_JOB_MEMORY_LIMIT:
|
|
break;
|
|
case JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO:
|
|
printf("jobcontrol: Requested process all stopped.");
|
|
bStop = TRUE;
|
|
break;
|
|
case JOB_OBJECT_MSG_NEW_PROCESS:
|
|
{
|
|
DWORD dwProcessId = (DWORD)pOverlapped;
|
|
tprintf("Process start (%d)\n", dwProcessId);
|
|
}
|
|
break;
|
|
case JOB_OBJECT_MSG_EXIT_PROCESS:
|
|
case JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS:
|
|
{
|
|
DWORD dwExitCode = 0;
|
|
DWORD dwProcessId = (DWORD)pOverlapped;
|
|
PCONTROLLED_PROCESS pProcess =
|
|
FindProcessInSet(pJobArgs->pSet, dwProcessId);
|
|
if (NULL != pProcess) {
|
|
if (!GetExitCodeProcess(pProcess->Process, &dwExitCode)) {
|
|
ErrorReport("GetExitCodeProcess");
|
|
fprintf(stdout, "jobcontrol: Unable to get exit code of process in job\n");
|
|
bStop = TRUE;
|
|
bFatalError = TRUE;
|
|
}
|
|
else if (dwExitCode != 0 && pProcess->ErrorExitFatal) {
|
|
bStop = TRUE;
|
|
bFatalError = TRUE;
|
|
}
|
|
|
|
tprintf("jobcontrol: Process %u exited (%u)\n",
|
|
dwProcessId, dwExitCode);
|
|
|
|
RemoveProcessFromSet(pJobArgs->pSet, dwProcessId);
|
|
if (pJobArgs->pSet->ProcessCount == 0) {
|
|
bStop = TRUE;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
// Should never happen
|
|
printf("jobcontrol: Unknown message type %08x", dwBytes);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (nowTime > endTime) {
|
|
printf("jobcontrol: Timed out\n");
|
|
}
|
|
|
|
tprintf("- Watching job (supplied = %u, %I64d, %I64d, timeout = %I64d)\n",
|
|
pJobArgs->dwTimeoutSeconds, endTime, nowTime, endTime - nowTime);
|
|
|
|
|
|
tprintf("Watch done (fatal error = %d, stop = %d)\n", bFatalError, bStop);
|
|
|
|
CloseHandle(hIoPort);
|
|
|
|
ExitThread((bFatalError || !bStop) ? (DWORD)-1 : 0);
|
|
}
|
|
|
|
static BOOL
|
|
StartJobThreads(PCONTROLLED_PROCESS_SET pSet)
|
|
{
|
|
DWORD i;
|
|
for (i = 0; i < pSet->ProcessCount; i++) {
|
|
tprintf("Starting process %d\n", pSet->Processes[i].ProcessId);
|
|
ResumeThread(pSet->Processes[i].Thread);
|
|
pSet->Processes[i].Thread = NULL;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
CleanupJobHandles(PCONTROLLED_PROCESS_SET pSet)
|
|
{
|
|
DWORD i;
|
|
PCONTROLLED_PROCESS pProcess = pSet->Processes;
|
|
for (i = 0; i < pSet->MaxProcessCount && pProcess->ProcessId != 0;
|
|
i++, pProcess++) {
|
|
CloseHandle(pProcess->Thread);
|
|
CloseHandle(pProcess->Process);
|
|
}
|
|
}
|
|
|
|
static DWORD
|
|
StartJobProcessesAndMonitor(HANDLE hJob,
|
|
PCONTROLLED_PROCESS_SET pSet,
|
|
DWORD dwTimeoutSeconds)
|
|
{
|
|
HANDLE hMonitorThread;
|
|
DWORD dwMonitorThreadExitCode;
|
|
MONITOR_THREAD_ARGS monitorArgs;
|
|
|
|
//
|
|
// Fire up monitoring thread so process can track all activity
|
|
//
|
|
monitorArgs.hJob = hJob;
|
|
monitorArgs.pSet = pSet;
|
|
monitorArgs.dwTimeoutSeconds = dwTimeoutSeconds;
|
|
monitorArgs.hMonitoringStarted = CreateEvent(NULL, TRUE, FALSE, NULL);
|
|
|
|
hMonitorThread = CreateThread(NULL, 0, MonitorJobThreadProc,
|
|
&monitorArgs, 0, NULL);
|
|
if (NULL == hMonitorThread) {
|
|
CloseHandle(monitorArgs.hMonitoringStarted);
|
|
ErrorReport("CreateThread");
|
|
return (DWORD)-1;
|
|
}
|
|
|
|
//
|
|
// Wait for monitoring thread to reach start point
|
|
//
|
|
WaitForSingleObject(monitorArgs.hMonitoringStarted, INFINITE);
|
|
|
|
//
|
|
// Resume process threads associated with job
|
|
//
|
|
StartJobThreads(pSet);
|
|
|
|
//
|
|
// Wait for monitoring thread to kick the bucket
|
|
//
|
|
WaitForSingleObject(hMonitorThread, INFINITE);
|
|
|
|
GetExitCodeThread(hMonitorThread, &dwMonitorThreadExitCode);
|
|
|
|
CloseHandle(hMonitorThread);
|
|
CloseHandle(monitorArgs.hMonitoringStarted);
|
|
|
|
CleanupJobHandles(pSet);
|
|
|
|
return dwMonitorThreadExitCode;
|
|
}
|
|
|
|
static const char * StringSeconds(time_t tElapsed)
|
|
{
|
|
static int snBuffer = 0;
|
|
static char rcbBuffer[10][10];
|
|
char *pszBuffer;
|
|
int hours, minutes, seconds;
|
|
|
|
snBuffer = (snBuffer + 1) % 10;
|
|
pszBuffer = rcbBuffer[snBuffer];
|
|
|
|
seconds = (int)tElapsed;
|
|
|
|
hours = seconds / 3600;
|
|
seconds -= hours * 3600;
|
|
minutes = seconds / 60;
|
|
seconds -= minutes * 60;
|
|
|
|
sprintf(pszBuffer, "%02d:%02d:%02d", hours, minutes, seconds);
|
|
return pszBuffer;
|
|
}
|
|
|
|
static const char * StringTicks(LARGE_INTEGER ticks)
|
|
{
|
|
LONGLONG seconds = ticks.QuadPart;
|
|
seconds /= 10000000;
|
|
|
|
return StringSeconds((time_t)seconds);
|
|
}
|
|
|
|
static void DisplaySeconds(const char* title, time_t tElapsed)
|
|
{
|
|
int hours, minutes, seconds;
|
|
seconds = (int)tElapsed;
|
|
|
|
hours = seconds / 3600;
|
|
seconds -= hours * 3600;
|
|
minutes = seconds / 60;
|
|
seconds -= minutes * 60;
|
|
|
|
printf("%s%02d:%02d:%02d\n", title, hours, minutes, seconds);
|
|
}
|
|
|
|
static void DisplayTicks(const char* title, LARGE_INTEGER ticks)
|
|
{
|
|
LONGLONG seconds = ticks.QuadPart;
|
|
seconds /= 10000000;
|
|
|
|
DisplaySeconds(title, (time_t)seconds);
|
|
}
|
|
|
|
static void ShowBasicAccountingInformation(HANDLE hJob,
|
|
time_t tStart,
|
|
time_t tEnd)
|
|
{
|
|
JOBOBJECT_BASIC_ACCOUNTING_INFORMATION jbai;
|
|
|
|
if (QueryInformationJobObject(hJob,
|
|
JobObjectBasicAccountingInformation,
|
|
&jbai,
|
|
sizeof(jbai),
|
|
NULL)) {
|
|
printf("Wall time elapsed: %s (User: %s, Kernel: %s)\n",
|
|
StringSeconds(tEnd - tStart),
|
|
StringTicks(jbai.TotalUserTime),
|
|
StringTicks(jbai.TotalKernelTime));
|
|
printf("Total processes: %8u\n", jbai.TotalProcesses);
|
|
printf("Page faults: %8u\n", jbai.TotalPageFaultCount);
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// User Commands
|
|
|
|
static int
|
|
CreateJobAndRun(int argc, LPCSTR argv[])
|
|
{
|
|
BOOL bSuccess = FALSE;
|
|
BOOL bShowBasicAccounting = FALSE;
|
|
DWORD dwMaxJobTime = 0;
|
|
DWORD dwResult = 0;
|
|
PCONTROLLED_PROCESS_SET pSet = NULL;
|
|
if (argc == 0) {
|
|
fprintf(stdout, "Missing <jobname> and options\n");
|
|
return -1;
|
|
}
|
|
|
|
tprintf("Creating job %s\n", argv[0]);
|
|
|
|
if (NULL == (ghJob = CreateJobObject(NULL, argv[0]))) {
|
|
ErrorReport("CreateJobObject");
|
|
return -1;
|
|
}
|
|
|
|
argv++;
|
|
argc--;
|
|
|
|
pSet = AllocateProcessSet(argc); // overallocation
|
|
|
|
bSuccess = TRUE;
|
|
while (argc != 0 && bSuccess) {
|
|
if (argc < 2) {
|
|
fprintf(stdout, "Unexpected end of arguments\n");
|
|
bSuccess = FALSE;
|
|
}
|
|
else if (!_strcmpi("/LimitJobUserTime", argv[0])) {
|
|
bSuccess = LimitJobUserTime(ghJob, JOB_OBJECT_LIMIT_JOB_TIME,
|
|
argv[1]);
|
|
}
|
|
else if (!_strcmpi("/LimitProcessUserTime", argv[0])) {
|
|
bSuccess = LimitJobUserTime(ghJob,JOB_OBJECT_LIMIT_PROCESS_TIME,
|
|
argv[1]);
|
|
}
|
|
else if (!_strcmpi("/LimitJobTime", argv[0])) {
|
|
bSuccess = ParseUInt32(argv[1], (PUINT32)&dwMaxJobTime);
|
|
}
|
|
else if (!_strcmpi("/B", argv[0])) {
|
|
bShowBasicAccounting = TRUE;
|
|
}
|
|
else if (!_strcmpi("/C", argv[0])) {
|
|
bSuccess = AddProcessToJob(ghJob, argv[1], pSet, FALSE);
|
|
argv += 1;
|
|
argc -= 1;
|
|
}
|
|
else if (!_strcmpi("/T", argv[0])) {
|
|
bSuccess = AddProcessToJob(ghJob, argv[1], pSet, TRUE);
|
|
argv += 1;
|
|
argc -= 1;
|
|
}
|
|
else if (!_strcmpi("/V", argv[0])) {
|
|
gbVerbose = TRUE;
|
|
}
|
|
else {
|
|
fprintf(stdout, "Unknown option \"%s\".\n", argv[0]);
|
|
bSuccess = FALSE;
|
|
}
|
|
argv += 1;
|
|
argc -= 1;
|
|
}
|
|
|
|
dwResult = (DWORD)-1;
|
|
if (bSuccess && pSet->ProcessCount != 0) {
|
|
time_t tStart;
|
|
time_t tEnd;
|
|
|
|
time(&tStart);
|
|
dwResult = StartJobProcessesAndMonitor(ghJob, pSet, dwMaxJobTime);
|
|
time(&tEnd);
|
|
|
|
if (bShowBasicAccounting) {
|
|
ShowBasicAccountingInformation(ghJob, tStart, tEnd);
|
|
}
|
|
}
|
|
|
|
FreeProcessSet(pSet);
|
|
TerminateJobObject(ghJob, (UINT)-1);
|
|
|
|
|
|
CloseHandle(ghJob);
|
|
|
|
return (int)dwResult;
|
|
}
|
|
|
|
static int
|
|
KillJob(int argc, LPCSTR argv[])
|
|
{
|
|
HANDLE hJob;
|
|
|
|
if (argc == 0) {
|
|
fprintf(stdout, "Missing <jobname> argument\n");
|
|
return -1;
|
|
}
|
|
else if (argc > 1) {
|
|
fprintf(stdout, "Unknown arguments passed to kill request\n");
|
|
Usage();
|
|
return -1;
|
|
}
|
|
else if (NULL == (hJob = CreateJobObject(NULL, argv[0]))) {
|
|
ErrorReport("CreateJobObject");
|
|
return -1;
|
|
}
|
|
else if (0 == TerminateJobObject(hJob, (UINT)-1)) {
|
|
ErrorReport("TerminateJobObject");
|
|
return -1;
|
|
}
|
|
tprintf("Terminated %s", argv[0]);
|
|
|
|
return 0;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Main
|
|
|
|
int main(int argc, const char* argv[])
|
|
{
|
|
BOOL bInJob;
|
|
if (!IsProcessInJob(GetCurrentProcess(), NULL, &bInJob)) {
|
|
ErrorReport("IsProcessInJob");
|
|
return -1;
|
|
}
|
|
|
|
if (argc == 2) {
|
|
// User is querying whether already in a job
|
|
if (_strcmpi(argv[1], "query") == 0) {
|
|
return bInJob ? 1 : 0;
|
|
}
|
|
}
|
|
else if (argc >= 3) {
|
|
if (_strcmpi(argv[1], "create") == 0) {
|
|
// Can't create a job inside a job
|
|
if (bInJob) {
|
|
fprintf(stdout, "Warning: jobcontrol launched inside a job\n");
|
|
}
|
|
SetConsoleCtrlHandler(CtrlHandler, TRUE);
|
|
return CreateJobAndRun(argc - 2, argv + 2);
|
|
}
|
|
else if (_strcmpi(argv[1], "kill") == 0) {
|
|
SetConsoleCtrlHandler(CtrlHandler, TRUE);
|
|
return KillJob(argc - 2, argv + 2);
|
|
}
|
|
}
|
|
Usage();
|
|
return -1;
|
|
}
|