123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359 |
- /**
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with this
- * work for additional information regarding copyright ownership. The ASF
- * licenses this file to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
- #include "winutils.h"
- #include <errno.h>
- #include <psapi.h>
- #include <malloc.h>
- #include <authz.h>
- #include <sddl.h>
- #ifdef PSAPI_VERSION
- #undef PSAPI_VERSION
- #endif
- #define PSAPI_VERSION 1
- #pragma comment(lib, "psapi.lib")
- #define NM_WSCE_IMPERSONATE_ALLOWED L"yarn.nodemanager.windows-secure-container-executor.impersonate.allowed"
- #define NM_WSCE_IMPERSONATE_DENIED L"yarn.nodemanager.windows-secure-container-executor.impersonate.denied"
- // The S4U impersonation access check mask. Arbitrary value (we use 1 for the service access check)
- #define SERVICE_IMPERSONATE_MASK 0x00000002
- // Name for tracking this logon process when registering with LSA
- static const char *LOGON_PROCESS_NAME="Hadoop Container Executor";
- // Name for token source, must be less or eq to TOKEN_SOURCE_LENGTH (currently 8) chars
- static const char *TOKEN_SOURCE_NAME = "HadoopEx";
- // List of different task related command line options supported by
- // winutils.
- typedef enum TaskCommandOptionType
- {
- TaskInvalid,
- TaskCreate,
- TaskCreateAsUser,
- TaskIsAlive,
- TaskKill,
- TaskProcessList
- } TaskCommandOption;
- //----------------------------------------------------------------------------
- // Function: GetLimit
- //
- // Description:
- // Get the resource limit value in long type given the command line argument.
- //
- // Returns:
- // TRUE: If successfully get the value
- // FALSE: otherwise
- static BOOL GetLimit(__in const wchar_t *str, __out long *value)
- {
- wchar_t *end = NULL;
- if (str == NULL || value == NULL) return FALSE;
- *value = wcstol(str, &end, 10);
- if (end == NULL || *end != '\0')
- {
- *value = -1;
- return FALSE;
- }
- else
- {
- return TRUE;
- }
- }
- //----------------------------------------------------------------------------
- // Function: ParseCommandLine
- //
- // Description:
- // Parses the given command line. On success, out param 'command' contains
- // the user specified command.
- //
- // Returns:
- // TRUE: If the command line is valid
- // FALSE: otherwise
- static BOOL ParseCommandLine(__in int argc,
- __in_ecount(argc) wchar_t *argv[],
- __out TaskCommandOption *command,
- __out_opt long *memory,
- __out_opt long *vcore)
- {
- *command = TaskInvalid;
- if (wcscmp(argv[0], L"task") != 0 )
- {
- return FALSE;
- }
- if (argc == 3) {
- if (wcscmp(argv[1], L"isAlive") == 0)
- {
- *command = TaskIsAlive;
- return TRUE;
- }
- if (wcscmp(argv[1], L"kill") == 0)
- {
- *command = TaskKill;
- return TRUE;
- }
- if (wcscmp(argv[1], L"processList") == 0)
- {
- *command = TaskProcessList;
- return TRUE;
- }
- }
- if (argc >= 4 && argc <= 8) {
- if (wcscmp(argv[1], L"create") == 0)
- {
- int i;
- for (i = 2; i < argc - 3; i++)
- {
- if (wcscmp(argv[i], L"-c") == 0)
- {
- if (vcore != NULL && !GetLimit(argv[i + 1], vcore))
- {
- return FALSE;
- }
- else
- {
- i++;
- continue;
- }
- }
- else if (wcscmp(argv[i], L"-m") == 0)
- {
- if (memory != NULL && !GetLimit(argv[i + 1], memory))
- {
- return FALSE;
- }
- else
- {
- i++;
- continue;
- }
- }
- else
- {
- break;
- }
- }
- if (argc - i != 2)
- return FALSE;
- *command = TaskCreate;
- return TRUE;
- }
- }
- if (argc >= 6) {
- if (wcscmp(argv[1], L"createAsUser") == 0)
- {
- *command = TaskCreateAsUser;
- return TRUE;
- }
- }
- return FALSE;
- }
- //----------------------------------------------------------------------------
- // Function: BuildImpersonateSecurityDescriptor
- //
- // Description:
- // Builds the security descriptor for the S4U impersonation permissions
- // This describes what users can be impersonated and what not
- //
- // Returns:
- // ERROR_SUCCESS: On success
- // GetLastError: otherwise
- //
- DWORD BuildImpersonateSecurityDescriptor(__out PSECURITY_DESCRIPTOR* ppSD) {
- DWORD dwError = ERROR_SUCCESS;
- size_t countAllowed = 0;
- PSID* allowedSids = NULL;
- size_t countDenied = 0;
- PSID* deniedSids = NULL;
- LPCWSTR value = NULL;
- WCHAR** tokens = NULL;
- size_t len = 0;
- size_t count = 0;
- size_t crt = 0;
- PSECURITY_DESCRIPTOR pSD = NULL;
- dwError = GetConfigValue(wsceConfigRelativePath, NM_WSCE_IMPERSONATE_ALLOWED, &len, &value);
- if (dwError) {
- ReportErrorCode(L"GetConfigValue:1", dwError);
- goto done;
- }
- if (0 == len) {
- dwError = ERROR_BAD_CONFIGURATION;
- ReportErrorCode(L"GetConfigValue:2", dwError);
- goto done;
- }
-
- dwError = SplitStringIgnoreSpaceW(len, value, L',', &count, &tokens);
- if (dwError) {
- ReportErrorCode(L"SplitStringIgnoreSpaceW:1", dwError);
- goto done;
- }
- allowedSids = LocalAlloc(LPTR, sizeof(PSID) * count);
- if (NULL == allowedSids) {
- dwError = GetLastError();
- ReportErrorCode(L"LocalAlloc:1", dwError);
- goto done;
- }
- for(crt = 0; crt < count; ++crt) {
- dwError = GetSidFromAcctNameW(tokens[crt], &allowedSids[crt]);
- if (dwError) {
- ReportErrorCode(L"GetSidFromAcctNameW:1", dwError);
- goto done;
- }
- }
- countAllowed = count;
- LocalFree(tokens);
- tokens = NULL;
- LocalFree((HLOCAL)value);
- value = NULL;
-
- dwError = GetConfigValue(wsceConfigRelativePath, NM_WSCE_IMPERSONATE_DENIED, &len, &value);
- if (dwError) {
- ReportErrorCode(L"GetConfigValue:3", dwError);
- goto done;
- }
- if (0 != len) {
- dwError = SplitStringIgnoreSpaceW(len, value, L',', &count, &tokens);
- if (dwError) {
- ReportErrorCode(L"SplitStringIgnoreSpaceW:2", dwError);
- goto done;
- }
- deniedSids = LocalAlloc(LPTR, sizeof(PSID) * count);
- if (NULL == allowedSids) {
- dwError = GetLastError();
- ReportErrorCode(L"LocalAlloc:2", dwError);
- goto done;
- }
- for(crt = 0; crt < count; ++crt) {
- dwError = GetSidFromAcctNameW(tokens[crt], &deniedSids[crt]);
- if (dwError) {
- ReportErrorCode(L"GetSidFromAcctNameW:2", dwError);
- goto done;
- }
- }
- countDenied = count;
- }
- dwError = BuildServiceSecurityDescriptor(
- SERVICE_IMPERSONATE_MASK,
- countAllowed, allowedSids,
- countDenied, deniedSids,
- NULL,
- &pSD);
- if (dwError) {
- ReportErrorCode(L"BuildServiceSecurityDescriptor", dwError);
- goto done;
- }
-
- *ppSD = pSD;
- pSD = NULL;
- done:
- if (pSD) LocalFree(pSD);
- if (tokens) LocalFree(tokens);
- if (allowedSids) LocalFree(allowedSids);
- if (deniedSids) LocalFree(deniedSids);
- return dwError;
- }
- //----------------------------------------------------------------------------
- // Function: AddNodeManagerAndUserACEsToObject
- //
- // Description:
- // Adds ACEs to grant NM and user the provided access mask over a given handle
- //
- // Returns:
- // ERROR_SUCCESS: on success
- //
- DWORD AddNodeManagerAndUserACEsToObject(
- __in HANDLE hObject,
- __in LPCWSTR user,
- __in ACCESS_MASK accessMask) {
- DWORD dwError = ERROR_SUCCESS;
- size_t countTokens = 0;
- size_t len = 0;
- LPCWSTR value = NULL;
- WCHAR** tokens = NULL;
- DWORD crt = 0;
- PACL pDacl = NULL;
- PSECURITY_DESCRIPTOR psdProcess = NULL;
- LPWSTR lpszOldDacl = NULL, lpszNewDacl = NULL;
- ULONG daclLen = 0;
- PACL pNewDacl = NULL;
- ACL_SIZE_INFORMATION si;
- DWORD dwNewAclSize = 0;
- PACE_HEADER pTempAce = NULL;
- BYTE sidTemp[SECURITY_MAX_SID_SIZE];
- DWORD cbSid = SECURITY_MAX_SID_SIZE;
- PSID tokenSid = NULL;
- // These hard-coded SIDs are allways added
- WELL_KNOWN_SID_TYPE forcesSidTypes[] = {
- WinLocalSystemSid,
- WinBuiltinAdministratorsSid};
- BOOL logSDs = IsDebuggerPresent(); // Check only once to avoid attach-while-running
-
-
- dwError = GetSecurityInfo(hObject,
- SE_KERNEL_OBJECT,
- DACL_SECURITY_INFORMATION,
- NULL,
- NULL,
- &pDacl,
- NULL,
- &psdProcess);
- if (dwError) {
- ReportErrorCode(L"GetSecurityInfo", dwError);
- goto done;
- }
- // This is debug only output for troubleshooting
- if (logSDs) {
- if (!ConvertSecurityDescriptorToStringSecurityDescriptor(
- psdProcess,
- SDDL_REVISION_1,
- DACL_SECURITY_INFORMATION,
- &lpszOldDacl,
- &daclLen)) {
- dwError = GetLastError();
- ReportErrorCode(L"ConvertSecurityDescriptorToStringSecurityDescriptor", dwError);
- goto done;
- }
- }
- ZeroMemory(&si, sizeof(si));
- if (!GetAclInformation(pDacl, &si, sizeof(si), AclSizeInformation)) {
- dwError = GetLastError();
- ReportErrorCode(L"GetAclInformation", dwError);
- goto done;
- }
- dwError = GetConfigValue(wsceConfigRelativePath, NM_WSCE_ALLOWED, &len, &value);
- if (ERROR_SUCCESS != dwError) {
- ReportErrorCode(L"GetConfigValue", dwError);
- goto done;
- }
- if (0 == len) {
- dwError = ERROR_BAD_CONFIGURATION;
- ReportErrorCode(L"GetConfigValue", dwError);
- goto done;
- }
- dwError = SplitStringIgnoreSpaceW(len, value, L',', &countTokens, &tokens);
- if (ERROR_SUCCESS != dwError) {
- ReportErrorCode(L"SplitStringIgnoreSpaceW", dwError);
- goto done;
- }
- // We're gonna add 1 ACE for each token found, +1 for user and +1 for each forcesSidTypes[]
- // ACCESS_ALLOWED_ACE struct contains the first DWORD of the SID
- //
- dwNewAclSize = si.AclBytesInUse +
- (DWORD)(countTokens + 1 + sizeof(forcesSidTypes)/sizeof(forcesSidTypes[0])) *
- (sizeof(ACCESS_ALLOWED_ACE) + SECURITY_MAX_SID_SIZE - sizeof(DWORD));
- pNewDacl = (PSID) LocalAlloc(LPTR, dwNewAclSize);
- if (!pNewDacl) {
- dwError = ERROR_OUTOFMEMORY;
- ReportErrorCode(L"LocalAlloc", dwError);
- goto done;
- }
- if (!InitializeAcl(pNewDacl, dwNewAclSize, ACL_REVISION)) {
- dwError = ERROR_OUTOFMEMORY;
- ReportErrorCode(L"InitializeAcl", dwError);
- goto done;
- }
- // Copy over old ACEs
- for (crt = 0; crt < si.AceCount; ++crt) {
- if (!GetAce(pDacl, crt, &pTempAce)) {
- dwError = ERROR_OUTOFMEMORY;
- ReportErrorCode(L"InitializeAcl", dwError);
- goto done;
- }
- if (!AddAce(pNewDacl, ACL_REVISION, MAXDWORD, pTempAce, pTempAce->AceSize)) {
- dwError = ERROR_OUTOFMEMORY;
- ReportErrorCode(L"InitializeAcl", dwError);
- goto done;
- }
- }
- // Add the configured allowed SIDs
- for (crt = 0; crt < countTokens; ++crt) {
- dwError = GetSidFromAcctNameW(tokens[crt], &tokenSid);
- if (ERROR_SUCCESS != dwError) {
- ReportErrorCode(L"GetSidFromAcctNameW", dwError);
- goto done;
- }
- if (!AddAccessAllowedAceEx(
- pNewDacl,
- ACL_REVISION_DS,
- CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE,
- PROCESS_ALL_ACCESS,
- tokenSid)) {
- dwError = GetLastError();
- ReportErrorCode(L"AddAccessAllowedAceEx:1", dwError);
- goto done;
- }
- LocalFree(tokenSid);
- tokenSid = NULL;
- }
- // add the forced SIDs ACE
- for (crt = 0; crt < sizeof(forcesSidTypes)/sizeof(forcesSidTypes[0]); ++crt) {
- cbSid = SECURITY_MAX_SID_SIZE;
- if (!CreateWellKnownSid(forcesSidTypes[crt], NULL, &sidTemp, &cbSid)) {
- dwError = GetLastError();
- ReportErrorCode(L"CreateWellKnownSid", dwError);
- goto done;
- }
- if (!AddAccessAllowedAceEx(
- pNewDacl,
- ACL_REVISION_DS,
- CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE,
- accessMask,
- (PSID) sidTemp)) {
- dwError = GetLastError();
- ReportErrorCode(L"AddAccessAllowedAceEx:2", dwError);
- goto done;
- }
- }
-
- // add the user ACE
- dwError = GetSidFromAcctNameW(user, &tokenSid);
- if (ERROR_SUCCESS != dwError) {
- ReportErrorCode(L"GetSidFromAcctNameW:user", dwError);
- goto done;
- }
- if (!AddAccessAllowedAceEx(
- pNewDacl,
- ACL_REVISION_DS,
- CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE,
- PROCESS_ALL_ACCESS,
- tokenSid)) {
- dwError = GetLastError();
- ReportErrorCode(L"AddAccessAllowedAceEx:3", dwError);
- goto done;
- }
- LocalFree(tokenSid);
- tokenSid = NULL;
- dwError = SetSecurityInfo(hObject,
- SE_KERNEL_OBJECT,
- DACL_SECURITY_INFORMATION,
- NULL,
- NULL,
- pNewDacl,
- NULL);
- if (dwError) {
- ReportErrorCode(L"SetSecurityInfo", dwError);
- goto done;
- }
- // This is debug only output for troubleshooting
- if (logSDs) {
- dwError = GetSecurityInfo(hObject,
- SE_KERNEL_OBJECT,
- DACL_SECURITY_INFORMATION,
- NULL,
- NULL,
- &pDacl,
- NULL,
- &psdProcess);
- if (dwError) {
- ReportErrorCode(L"GetSecurityInfo:2", dwError);
- goto done;
- }
- if (!ConvertSecurityDescriptorToStringSecurityDescriptor(
- psdProcess,
- SDDL_REVISION_1,
- DACL_SECURITY_INFORMATION,
- &lpszNewDacl,
- &daclLen)) {
- dwError = GetLastError();
- ReportErrorCode(L"ConvertSecurityDescriptorToStringSecurityDescriptor:2", dwError);
- goto done;
- }
- LogDebugMessage(L"Old DACL: %ls\nNew DACL: %ls\n", lpszOldDacl, lpszNewDacl);
- }
-
- done:
- if (tokenSid) LocalFree(tokenSid);
- if (pNewDacl) LocalFree(pNewDacl);
- if (lpszOldDacl) LocalFree(lpszOldDacl);
- if (lpszNewDacl) LocalFree(lpszNewDacl);
- if (psdProcess) LocalFree(psdProcess);
- return dwError;
- }
- //----------------------------------------------------------------------------
- // Function: ValidateImpersonateAccessCheck
- //
- // Description:
- // Performs the access check for S4U impersonation
- //
- // Returns:
- // ERROR_SUCCESS: On success
- // ERROR_ACCESS_DENIED, GetLastError: otherwise
- //
- DWORD ValidateImpersonateAccessCheck(__in HANDLE logonHandle) {
- DWORD dwError = ERROR_SUCCESS;
- PSECURITY_DESCRIPTOR pSD = NULL;
- LUID luidUnused;
- AUTHZ_ACCESS_REQUEST request;
- AUTHZ_ACCESS_REPLY reply;
- DWORD authError = ERROR_SUCCESS;
- DWORD saclResult = 0;
- ACCESS_MASK grantedMask = 0;
- AUTHZ_RESOURCE_MANAGER_HANDLE hManager = NULL;
- AUTHZ_CLIENT_CONTEXT_HANDLE hAuthzToken = NULL;
- ZeroMemory(&luidUnused, sizeof(luidUnused));
- ZeroMemory(&request, sizeof(request));
- ZeroMemory(&reply, sizeof(reply));
- dwError = BuildImpersonateSecurityDescriptor(&pSD);
- if (dwError) {
- ReportErrorCode(L"BuildImpersonateSecurityDescriptor", dwError);
- goto done;
- }
- request.DesiredAccess = MAXIMUM_ALLOWED;
- reply.Error = &authError;
- reply.SaclEvaluationResults = &saclResult;
- reply.ResultListLength = 1;
- reply.GrantedAccessMask = &grantedMask;
- if (!AuthzInitializeResourceManager(
- AUTHZ_RM_FLAG_NO_AUDIT,
- NULL, // pfnAccessCheck
- NULL, // pfnComputeDynamicGroups
- NULL, // pfnFreeDynamicGroups
- NULL, // szResourceManagerName
- &hManager)) {
- dwError = GetLastError();
- ReportErrorCode(L"AuthzInitializeResourceManager", dwError);
- goto done;
- }
- if (!AuthzInitializeContextFromToken(
- 0,
- logonHandle,
- hManager,
- NULL, // expiration time
- luidUnused, // not used
- NULL, // callback args
- &hAuthzToken)) {
- dwError = GetLastError();
- ReportErrorCode(L"AuthzInitializeContextFromToken", dwError);
- goto done;
- }
- if (!AuthzAccessCheck(
- 0,
- hAuthzToken,
- &request,
- NULL, // AuditEvent
- pSD,
- NULL, // OptionalSecurityDescriptorArray
- 0, // OptionalSecurityDescriptorCount
- &reply,
- NULL // phAccessCheckResults
- )) {
- dwError = GetLastError();
- ReportErrorCode(L"AuthzAccessCheck", dwError);
- goto done;
- }
- LogDebugMessage(L"AutzAccessCheck: Error:%d sacl:%d access:%d\n",
- authError, saclResult, grantedMask);
-
- if (authError != ERROR_SUCCESS) {
- ReportErrorCode(L"AuthzAccessCheck:REPLY:1", authError);
- dwError = authError;
- }
- else if (!(grantedMask & SERVICE_IMPERSONATE_MASK)) {
- ReportErrorCode(L"AuthzAccessCheck:REPLY:2", ERROR_ACCESS_DENIED);
- dwError = ERROR_ACCESS_DENIED;
- }
- done:
- if (hAuthzToken) AuthzFreeContext(hAuthzToken);
- if (hManager) AuthzFreeResourceManager(hManager);
- if (pSD) LocalFree(pSD);
- return dwError;
- }
- //----------------------------------------------------------------------------
- // Function: CreateTaskImpl
- //
- // Description:
- // Creates a task via a jobobject. Outputs the
- // appropriate information to stdout on success, or stderr on failure.
- // logonHandle may be NULL, in this case the current logon will be utilized for the
- // created process
- //
- // Returns:
- // ERROR_SUCCESS: On success
- // GetLastError: otherwise
- DWORD CreateTaskImpl(__in_opt HANDLE logonHandle, __in PCWSTR jobObjName,__in PWSTR cmdLine,
- __in LPCWSTR userName, __in long memory, __in long cpuRate)
- {
- DWORD dwErrorCode = ERROR_SUCCESS;
- DWORD exitCode = EXIT_FAILURE;
- DWORD currDirCnt = 0;
- STARTUPINFO si;
- PROCESS_INFORMATION pi;
- HANDLE jobObject = NULL;
- JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli = { 0 };
- void * envBlock = NULL;
- WCHAR secureJobNameBuffer[MAX_PATH];
- LPCWSTR secureJobName = jobObjName;
- wchar_t* curr_dir = NULL;
- FILE *stream = NULL;
- if (NULL != logonHandle) {
- dwErrorCode = ValidateImpersonateAccessCheck(logonHandle);
- if (dwErrorCode) {
- ReportErrorCode(L"ValidateImpersonateAccessCheck", dwErrorCode);
- return dwErrorCode;
- }
- dwErrorCode = GetSecureJobObjectName(jobObjName, MAX_PATH, secureJobNameBuffer);
- if (dwErrorCode) {
- ReportErrorCode(L"GetSecureJobObjectName", dwErrorCode);
- return dwErrorCode;
- }
- secureJobName = secureJobNameBuffer;
- }
- // Create un-inheritable job object handle and set job object to terminate
- // when last handle is closed. So winutils.exe invocation has the only open
- // job object handle. Exit of winutils.exe ensures termination of job object.
- // Either a clean exit of winutils or crash or external termination.
- jobObject = CreateJobObject(NULL, secureJobName);
- dwErrorCode = GetLastError();
- if(jobObject == NULL || dwErrorCode == ERROR_ALREADY_EXISTS)
- {
- ReportErrorCode(L"CreateJobObject", dwErrorCode);
- return dwErrorCode;
- }
- jeli.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
- if (memory > 0)
- {
- jeli.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_JOB_MEMORY;
- jeli.ProcessMemoryLimit = ((SIZE_T) memory) * 1024 * 1024;
- jeli.JobMemoryLimit = ((SIZE_T) memory) * 1024 * 1024;
- }
- if(SetInformationJobObject(jobObject,
- JobObjectExtendedLimitInformation,
- &jeli,
- sizeof(jeli)) == 0)
- {
- dwErrorCode = GetLastError();
- ReportErrorCode(L"SetInformationJobObject", dwErrorCode);
- CloseHandle(jobObject);
- return dwErrorCode;
- }
- #ifdef NTDDI_WIN8
- if (cpuRate > 0)
- {
- JOBOBJECT_CPU_RATE_CONTROL_INFORMATION jcrci = { 0 };
- SYSTEM_INFO sysinfo;
- GetSystemInfo(&sysinfo);
- jcrci.ControlFlags = JOB_OBJECT_CPU_RATE_CONTROL_ENABLE |
- JOB_OBJECT_CPU_RATE_CONTROL_HARD_CAP;
- jcrci.CpuRate = min(10000, cpuRate);
- if(SetInformationJobObject(jobObject, JobObjectCpuRateControlInformation,
- &jcrci, sizeof(jcrci)) == 0)
- {
- dwErrorCode = GetLastError();
- CloseHandle(jobObject);
- return dwErrorCode;
- }
- }
- #endif
- if (logonHandle != NULL) {
- dwErrorCode = AddNodeManagerAndUserACEsToObject(jobObject, userName, JOB_OBJECT_ALL_ACCESS);
- if (dwErrorCode) {
- ReportErrorCode(L"AddNodeManagerAndUserACEsToObject", dwErrorCode);
- CloseHandle(jobObject);
- return dwErrorCode;
- }
- }
- if(AssignProcessToJobObject(jobObject, GetCurrentProcess()) == 0)
- {
- dwErrorCode = GetLastError();
- ReportErrorCode(L"AssignProcessToJobObject", dwErrorCode);
- CloseHandle(jobObject);
- return dwErrorCode;
- }
- // the child JVM uses this env var to send the task OS process identifier
- // to the TaskTracker. We pass the job object name.
- if(SetEnvironmentVariable(L"JVM_PID", jobObjName) == 0)
- {
- dwErrorCode = GetLastError();
- ReportErrorCode(L"SetEnvironmentVariable", dwErrorCode);
- // We have to explictly Terminate, passing in the error code
- // simply closing the job would kill our own process with success exit status
- TerminateJobObject(jobObject, dwErrorCode);
- return dwErrorCode;
- }
- ZeroMemory( &si, sizeof(si) );
- si.cb = sizeof(si);
- ZeroMemory( &pi, sizeof(pi) );
- if( logonHandle != NULL ) {
- // create user environment for this logon
- if(!CreateEnvironmentBlock(&envBlock,
- logonHandle,
- TRUE )) {
- dwErrorCode = GetLastError();
- ReportErrorCode(L"CreateEnvironmentBlock", dwErrorCode);
- // We have to explictly Terminate, passing in the error code
- // simply closing the job would kill our own process with success exit status
- TerminateJobObject(jobObject, dwErrorCode);
- return dwErrorCode;
- }
- }
- // Get the required buffer size first
- currDirCnt = GetCurrentDirectory(0, NULL);
- if (0 < currDirCnt) {
- curr_dir = (wchar_t*) alloca(currDirCnt * sizeof(wchar_t));
- assert(curr_dir);
- currDirCnt = GetCurrentDirectory(currDirCnt, curr_dir);
- }
-
- if (0 == currDirCnt) {
- dwErrorCode = GetLastError();
- ReportErrorCode(L"GetCurrentDirectory", dwErrorCode);
- // We have to explictly Terminate, passing in the error code
- // simply closing the job would kill our own process with success exit status
- TerminateJobObject(jobObject, dwErrorCode);
- return dwErrorCode;
- }
- dwErrorCode = ERROR_SUCCESS;
- if (logonHandle == NULL) {
- if (!CreateProcess(
- NULL, // ApplicationName
- cmdLine, // command line
- NULL, // process security attributes
- NULL, // thread security attributes
- TRUE, // inherit handles
- 0, // creation flags
- NULL, // environment
- curr_dir, // current directory
- &si, // startup info
- &pi)) { // process info
- dwErrorCode = GetLastError();
- ReportErrorCode(L"CreateProcess", dwErrorCode);
- }
- // task create (w/o createAsUser) does not need the ACEs change on the process
- goto create_process_done;
- }
- // From here on is the secure S4U implementation for CreateProcessAsUser
-
- // We desire to grant process access to NM so that it can interogate process status
- // and resource utilization. Passing in a security descriptor though results in the
- // S4U privilege checks being done against that SD and CreateProcessAsUser fails.
- // So instead we create the process suspended and then we add the desired ACEs.
- //
- if (!CreateProcessAsUser(
- logonHandle, // logon token handle
- NULL, // Application handle
- cmdLine, // command line
- NULL, // process security attributes
- NULL, // thread security attributes
- FALSE, // inherit handles
- CREATE_UNICODE_ENVIRONMENT | CREATE_SUSPENDED, // creation flags
- envBlock, // environment
- curr_dir, // current directory
- &si, // startup info
- &pi)) { // process info
- dwErrorCode = GetLastError();
- ReportErrorCode(L"CreateProcessAsUser", dwErrorCode);
- goto create_process_done;
- }
- dwErrorCode = AddNodeManagerAndUserACEsToObject(pi.hProcess, userName, PROCESS_ALL_ACCESS);
- if (dwErrorCode) {
- ReportErrorCode(L"AddNodeManagerAndUserACEsToObject", dwErrorCode);
- goto create_process_done;
- }
- if (-1 == ResumeThread(pi.hThread)) {
- dwErrorCode = GetLastError();
- ReportErrorCode(L"ResumeThread", dwErrorCode);
- goto create_process_done;
- }
- create_process_done:
- if (dwErrorCode) {
- if( envBlock != NULL ) {
- DestroyEnvironmentBlock( envBlock );
- envBlock = NULL;
- }
- // We have to explictly Terminate, passing in the error code
- // simply closing the job would kill our own process with success exit status
- TerminateJobObject(jobObject, dwErrorCode);
- // This is tehnically dead code, we cannot reach this condition
- return dwErrorCode;
- }
- CloseHandle(pi.hThread);
- // Wait until child process exits.
- WaitForSingleObject( pi.hProcess, INFINITE );
- if(GetExitCodeProcess(pi.hProcess, &exitCode) == 0)
- {
- dwErrorCode = GetLastError();
- }
- CloseHandle( pi.hProcess );
- if( envBlock != NULL ) {
- DestroyEnvironmentBlock( envBlock );
- envBlock = NULL;
- }
- // Terminate job object so that all spawned processes are also killed.
- // This is needed because once this process closes the handle to the job
- // object and none of the spawned objects have the handle open (via
- // inheritance on creation) then it will not be possible for any other external
- // program (say winutils task kill) to terminate this job object via its name.
- if(TerminateJobObject(jobObject, exitCode) == 0)
- {
- dwErrorCode = GetLastError();
- }
- // comes here only on failure of TerminateJobObject
- CloseHandle(jobObject);
- if(dwErrorCode != ERROR_SUCCESS)
- {
- return dwErrorCode;
- }
- return exitCode;
- }
- //----------------------------------------------------------------------------
- // Function: CreateTask
- //
- // Description:
- // Creates a task via a jobobject. Outputs the
- // appropriate information to stdout on success, or stderr on failure.
- //
- // Returns:
- // ERROR_SUCCESS: On success
- // GetLastError: otherwise
- DWORD CreateTask(__in PCWSTR jobObjName,__in PWSTR cmdLine, __in long memory, __in long cpuRate)
- {
- // call with null logon in order to create tasks utilizing the current logon
- return CreateTaskImpl( NULL, jobObjName, cmdLine, NULL, memory, cpuRate);
- }
- //----------------------------------------------------------------------------
- // Function: CreateTaskAsUser
- //
- // Description:
- // Creates a task via a jobobject. Outputs the
- // appropriate information to stdout on success, or stderr on failure.
- //
- // Returns:
- // ERROR_SUCCESS: On success
- // GetLastError: otherwise
- DWORD CreateTaskAsUser(__in PCWSTR jobObjName,
- __in PCWSTR user, __in PCWSTR pidFilePath, __in PWSTR cmdLine)
- {
- DWORD err = ERROR_SUCCESS;
- DWORD exitCode = EXIT_FAILURE;
- ULONG authnPkgId;
- HANDLE lsaHandle = INVALID_HANDLE_VALUE;
- PROFILEINFO pi;
- BOOL profileIsLoaded = FALSE;
- FILE* pidFile = NULL;
- DWORD retLen = 0;
- HANDLE logonHandle = NULL;
- errno_t pidErrNo = 0;
- err = EnableImpersonatePrivileges();
- if( err != ERROR_SUCCESS ) {
- ReportErrorCode(L"EnableImpersonatePrivileges", err);
- goto done;
- }
- err = RegisterWithLsa(LOGON_PROCESS_NAME ,&lsaHandle);
- if( err != ERROR_SUCCESS ) {
- ReportErrorCode(L"RegisterWithLsa", err);
- goto done;
- }
- err = LookupKerberosAuthenticationPackageId( lsaHandle, &authnPkgId );
- if( err != ERROR_SUCCESS ) {
- ReportErrorCode(L"LookupKerberosAuthenticationPackageId", err);
- goto done;
- }
- err = CreateLogonTokenForUser(lsaHandle,
- LOGON_PROCESS_NAME,
- TOKEN_SOURCE_NAME,
- authnPkgId,
- user,
- &logonHandle);
- if( err != ERROR_SUCCESS ) {
- ReportErrorCode(L"CreateLogonTokenForUser", err);
- goto done;
- }
- err = LoadUserProfileForLogon(logonHandle, &pi);
- if( err != ERROR_SUCCESS ) {
- ReportErrorCode(L"LoadUserProfileForLogon", err);
- goto done;
- }
- profileIsLoaded = TRUE;
- // Create the PID file
- pidErrNo = _wfopen_s(&pidFile, pidFilePath, L"w");
- if (pidErrNo) {
- err = GetLastError();
- ReportErrorCode(L"_wfopen:pidFilePath", err);
- goto done;
- }
- if (0 > fprintf_s(pidFile, "%ls", jobObjName)) {
- err = GetLastError();
- }
-
- fclose(pidFile);
-
- if (err != ERROR_SUCCESS) {
- ReportErrorCode(L"fprintf_s:pidFilePath", err);
- goto done;
- }
- err = CreateTaskImpl(logonHandle, jobObjName, cmdLine, user, -1, -1);
- done:
- if( profileIsLoaded ) {
- UnloadProfileForLogon( logonHandle, &pi );
- profileIsLoaded = FALSE;
- }
- if( logonHandle != NULL ) {
- CloseHandle(logonHandle);
- }
- if (INVALID_HANDLE_VALUE != lsaHandle) {
- UnregisterWithLsa(lsaHandle);
- }
- return err;
- }
- //----------------------------------------------------------------------------
- // Function: IsTaskAlive
- //
- // Description:
- // Checks if a task is alive via a jobobject. Outputs the
- // appropriate information to stdout on success, or stderr on failure.
- //
- // Returns:
- // ERROR_SUCCESS: On success
- // GetLastError: otherwise
- DWORD IsTaskAlive(const WCHAR* jobObjName, int* isAlive, int* procsInJob)
- {
- PJOBOBJECT_BASIC_PROCESS_ID_LIST procList;
- HANDLE jobObject = NULL;
- int numProcs = 100;
- WCHAR secureJobNameBuffer[MAX_PATH];
- *isAlive = FALSE;
-
- jobObject = OpenJobObject(JOB_OBJECT_QUERY, FALSE, jobObjName);
- if(jobObject == NULL)
- {
- // Try Global\...
- DWORD err = GetSecureJobObjectName(jobObjName, MAX_PATH, secureJobNameBuffer);
- if (err) {
- ReportErrorCode(L"GetSecureJobObjectName", err);
- return err;
- }
- jobObject = OpenJobObject(JOB_OBJECT_QUERY, FALSE, secureJobNameBuffer);
- }
-
- if(jobObject == NULL)
- {
- DWORD err = GetLastError();
- return err;
- }
- if(jobObject == NULL)
- {
- DWORD err = GetLastError();
- if(err == ERROR_FILE_NOT_FOUND)
- {
- // job object does not exist. assume its not alive
- return ERROR_SUCCESS;
- }
- return err;
- }
- procList = (PJOBOBJECT_BASIC_PROCESS_ID_LIST) LocalAlloc(LPTR, sizeof (JOBOBJECT_BASIC_PROCESS_ID_LIST) + numProcs*32);
- if (!procList)
- {
- DWORD err = GetLastError();
- CloseHandle(jobObject);
- return err;
- }
- if(QueryInformationJobObject(jobObject, JobObjectBasicProcessIdList, procList, sizeof(JOBOBJECT_BASIC_PROCESS_ID_LIST)+numProcs*32, NULL) == 0)
- {
- DWORD err = GetLastError();
- if(err != ERROR_MORE_DATA)
- {
- CloseHandle(jobObject);
- LocalFree(procList);
- return err;
- }
- }
- if(procList->NumberOfAssignedProcesses > 0)
- {
- *isAlive = TRUE;
- *procsInJob = procList->NumberOfAssignedProcesses;
- }
- LocalFree(procList);
- return ERROR_SUCCESS;
- }
- //----------------------------------------------------------------------------
- // Function: PrintTaskProcessList
- //
- // Description:
- // Prints resource usage of all processes in the task jobobject
- //
- // Returns:
- // ERROR_SUCCESS: On success
- // GetLastError: otherwise
- DWORD PrintTaskProcessList(const WCHAR* jobObjName)
- {
- DWORD i;
- PJOBOBJECT_BASIC_PROCESS_ID_LIST procList;
- int numProcs = 100;
- WCHAR secureJobNameBuffer[MAX_PATH];
- HANDLE jobObject = NULL;
- jobObject = OpenJobObject(JOB_OBJECT_QUERY, FALSE, jobObjName);
- if(jobObject == NULL)
- {
- // Try Global\...
- DWORD err = GetSecureJobObjectName(jobObjName, MAX_PATH, secureJobNameBuffer);
- if (err) {
- ReportErrorCode(L"GetSecureJobObjectName", err);
- return err;
- }
- jobObject = OpenJobObject(JOB_OBJECT_QUERY, FALSE, secureJobNameBuffer);
- }
-
- if(jobObject == NULL)
- {
- DWORD err = GetLastError();
- return err;
- }
- procList = (PJOBOBJECT_BASIC_PROCESS_ID_LIST) LocalAlloc(LPTR, sizeof (JOBOBJECT_BASIC_PROCESS_ID_LIST) + numProcs*32);
- if (!procList)
- {
- DWORD err = GetLastError();
- CloseHandle(jobObject);
- return err;
- }
- while(QueryInformationJobObject(jobObject, JobObjectBasicProcessIdList, procList, sizeof(JOBOBJECT_BASIC_PROCESS_ID_LIST)+numProcs*32, NULL) == 0)
- {
- DWORD err = GetLastError();
- if(err != ERROR_MORE_DATA)
- {
- CloseHandle(jobObject);
- LocalFree(procList);
- return err;
- }
- numProcs = procList->NumberOfAssignedProcesses;
- LocalFree(procList);
- procList = (PJOBOBJECT_BASIC_PROCESS_ID_LIST) LocalAlloc(LPTR, sizeof (JOBOBJECT_BASIC_PROCESS_ID_LIST) + numProcs*32);
- if (procList == NULL)
- {
- err = GetLastError();
- CloseHandle(jobObject);
- return err;
- }
- }
- for(i=0; i<procList->NumberOfProcessIdsInList; ++i)
- {
- HANDLE hProcess = OpenProcess( PROCESS_QUERY_INFORMATION, FALSE, (DWORD)procList->ProcessIdList[i] );
- if( hProcess != NULL )
- {
- PROCESS_MEMORY_COUNTERS_EX pmc;
- if ( GetProcessMemoryInfo( hProcess, (PPROCESS_MEMORY_COUNTERS)&pmc, sizeof(pmc)) )
- {
- FILETIME create, exit, kernel, user;
- if( GetProcessTimes( hProcess, &create, &exit, &kernel, &user) )
- {
- ULARGE_INTEGER kernelTime, userTime;
- ULONGLONG cpuTimeMs;
- kernelTime.HighPart = kernel.dwHighDateTime;
- kernelTime.LowPart = kernel.dwLowDateTime;
- userTime.HighPart = user.dwHighDateTime;
- userTime.LowPart = user.dwLowDateTime;
- cpuTimeMs = (kernelTime.QuadPart+userTime.QuadPart)/10000;
- fwprintf_s(stdout, L"%Iu,%Iu,%Iu,%I64u\n", procList->ProcessIdList[i], pmc.PrivateUsage, pmc.WorkingSetSize, cpuTimeMs);
- }
- }
- CloseHandle( hProcess );
- }
- }
- LocalFree(procList);
- CloseHandle(jobObject);
- return ERROR_SUCCESS;
- }
- //----------------------------------------------------------------------------
- // Function: Task
- //
- // Description:
- // Manages a task via a jobobject (create/isAlive/kill). Outputs the
- // appropriate information to stdout on success, or stderr on failure.
- //
- // Returns:
- // ERROR_SUCCESS: On success
- // Error code otherwise: otherwise
- int Task(__in int argc, __in_ecount(argc) wchar_t *argv[])
- {
- DWORD dwErrorCode = ERROR_SUCCESS;
- TaskCommandOption command = TaskInvalid;
- long memory = -1;
- long cpuRate = -1;
- wchar_t* cmdLine = NULL;
- wchar_t buffer[16*1024] = L""; // 32K max command line
- size_t charCountBufferLeft = sizeof(buffer)/sizeof(wchar_t);
- int crtArgIndex = 0;
- size_t argLen = 0;
- size_t wscatErr = 0;
- wchar_t* insertHere = NULL;
- enum {
- ARGC_JOBOBJECTNAME = 2,
- ARGC_USERNAME,
- ARGC_PIDFILE,
- ARGC_COMMAND,
- ARGC_COMMAND_ARGS
- };
- if (!ParseCommandLine(argc, argv, &command, &memory, &cpuRate)) {
- dwErrorCode = ERROR_INVALID_COMMAND_LINE;
- fwprintf(stderr, L"Incorrect command line arguments.\n\n");
- TaskUsage();
- goto TaskExit;
- }
- if (command == TaskCreate)
- {
- // Create the task jobobject
- //
- dwErrorCode = CreateTask(argv[argc-2], argv[argc-1], memory, cpuRate);
- if (dwErrorCode != ERROR_SUCCESS)
- {
- ReportErrorCode(L"CreateTask", dwErrorCode);
- goto TaskExit;
- }
- } else if (command == TaskCreateAsUser)
- {
- // Create the task jobobject as a domain user
- // createAsUser accepts an open list of arguments. All arguments after the command are
- // to be passed as argumrnts to the command itself.Here we're concatenating all
- // arguments after the command into a single arg entry.
- //
- cmdLine = argv[ARGC_COMMAND];
- if (argc > ARGC_COMMAND_ARGS) {
- crtArgIndex = ARGC_COMMAND;
- insertHere = buffer;
- while (crtArgIndex < argc) {
- argLen = wcslen(argv[crtArgIndex]);
- wscatErr = wcscat_s(insertHere, charCountBufferLeft, argv[crtArgIndex]);
- switch (wscatErr) {
- case 0:
- // 0 means success;
- break;
- case EINVAL:
- dwErrorCode = ERROR_INVALID_PARAMETER;
- goto TaskExit;
- case ERANGE:
- dwErrorCode = ERROR_INSUFFICIENT_BUFFER;
- goto TaskExit;
- default:
- // This case is not MSDN documented.
- dwErrorCode = ERROR_GEN_FAILURE;
- goto TaskExit;
- }
- insertHere += argLen;
- charCountBufferLeft -= argLen;
- insertHere[0] = L' ';
- insertHere += 1;
- charCountBufferLeft -= 1;
- insertHere[0] = 0;
- ++crtArgIndex;
- }
- cmdLine = buffer;
- }
- dwErrorCode = CreateTaskAsUser(
- argv[ARGC_JOBOBJECTNAME], argv[ARGC_USERNAME], argv[ARGC_PIDFILE], cmdLine);
- if (dwErrorCode != ERROR_SUCCESS)
- {
- ReportErrorCode(L"CreateTaskAsUser", dwErrorCode);
- goto TaskExit;
- }
- } else if (command == TaskIsAlive)
- {
- // Check if task jobobject
- //
- int isAlive;
- int numProcs;
- dwErrorCode = IsTaskAlive(argv[2], &isAlive, &numProcs);
- if (dwErrorCode != ERROR_SUCCESS)
- {
- ReportErrorCode(L"IsTaskAlive", dwErrorCode);
- goto TaskExit;
- }
- // Output the result
- if(isAlive == TRUE)
- {
- fwprintf(stdout, L"IsAlive,%d\n", numProcs);
- }
- else
- {
- dwErrorCode = ERROR_TASK_NOT_ALIVE;
- ReportErrorCode(L"IsTaskAlive returned false", dwErrorCode);
- goto TaskExit;
- }
- } else if (command == TaskKill)
- {
- // Check if task jobobject
- //
- dwErrorCode = KillTask(argv[2]);
- if (dwErrorCode != ERROR_SUCCESS)
- {
- ReportErrorCode(L"KillTask", dwErrorCode);
- goto TaskExit;
- }
- } else if (command == TaskProcessList)
- {
- // Check if task jobobject
- //
- dwErrorCode = PrintTaskProcessList(argv[2]);
- if (dwErrorCode != ERROR_SUCCESS)
- {
- ReportErrorCode(L"PrintTaskProcessList", dwErrorCode);
- goto TaskExit;
- }
- } else
- {
- // Should not happen
- //
- assert(FALSE);
- }
- TaskExit:
- ReportErrorCode(L"TaskExit:", dwErrorCode);
- return dwErrorCode;
- }
- void TaskUsage()
- {
- // Hadoop code checks for this string to determine if
- // jobobject's are being used.
- // ProcessTree.isSetsidSupported()
- fwprintf(stdout, L"\
- Usage: task create [OPTIONS] [TASKNAME] [COMMAND_LINE]\n\
- Creates a new task job object with taskname and options to set CPU\n\
- and memory limits on the job object\n\
- \n\
- OPTIONS: -c [cpu rate] set the cpu rate limit on the job object.\n\
- -m [memory] set the memory limit on the job object.\n\
- The cpu limit is an integral value of percentage * 100. The memory\n\
- limit is an integral number of memory in MB. \n\
- The limit will not be set if 0 or negative value is passed in as\n\
- parameter(s).\n\
- \n\
- task createAsUser [TASKNAME] [USERNAME] [PIDFILE] [COMMAND_LINE]\n\
- Creates a new task jobobject with taskname as the user provided\n\
- \n\
- task isAlive [TASKNAME]\n\
- Checks if task job object is alive\n\
- \n\
- task kill [TASKNAME]\n\
- Kills task job object\n\
- \n\
- task processList [TASKNAME]\n\
- Prints to stdout a list of processes in the task\n\
- along with their resource usage. One process per line\n\
- and comma separated info per process\n\
- ProcessId,VirtualMemoryCommitted(bytes),\n\
- WorkingSetSize(bytes),CpuTime(Millisec,Kernel+User)\n");
- }
|