DWORD CreateTaskImpl()

in hadoop-common-project/hadoop-common/src/main/winutils/task.c [640:889]


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;
}