Index: include/ndk/pstypes.h =================================================================== --- include/ndk/pstypes.h (revision 70214) +++ include/ndk/pstypes.h (working copy) @@ -852,6 +852,58 @@ #ifndef NTOS_MODE_USER // +// Job msg flags +// + +#define JOB_OBJECT_MSG_END_OF_JOB_TIME 1 +#define JOB_OBJECT_MSG_END_OF_PROCESS_TIME 2 +#define JOB_OBJECT_MSG_ACTIVE_PROCESS_LIMIT 3 +#define JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO 4 +#define JOB_OBJECT_MSG_NEW_PROCESS 6 +#define JOB_OBJECT_MSG_EXIT_PROCESS 7 +#define JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS 8 +#define JOB_OBJECT_MSG_PROCESS_MEMORY_LIMIT 9 +#define JOB_OBJECT_MSG_JOB_MEMORY_LIMIT 10 +#define JOB_OBJECT_MSG_NOTIFICATION_LIMIT 11 +#define JOB_OBJECT_MSG_JOB_CYCLE_TIME_LIMIT 12 + +// +// Job Information Structures for NtQuery/SetInformationJobObject +// + +typedef struct _JOBOBJECT_BASIC_LIMIT_INFORMATION { + LARGE_INTEGER PerProcessUserTimeLimit; + LARGE_INTEGER PerJobUserTimeLimit; + ULONG LimitFlags; + SIZE_T MinimumWorkingSetSize; + SIZE_T MaximumWorkingSetSize; + ULONG ActiveProcessLimit; + ULONG_PTR Affinity; + ULONG PriorityClass; + ULONG SchedulingClass; +} JOBOBJECT_BASIC_LIMIT_INFORMATION, *PJOBOBJECT_BASIC_LIMIT_INFORMATION; + +typedef struct _JOBOBJECT_EXTENDED_LIMIT_INFORMATION { + JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation; + IO_COUNTERS IoInfo; + SIZE_T ProcessMemoryLimit; + SIZE_T JobMemoryLimit; + SIZE_T PeakProcessMemoryUsed; + SIZE_T PeakJobMemoryUsed; +} JOBOBJECT_EXTENDED_LIMIT_INFORMATION, *PJOBOBJECT_EXTENDED_LIMIT_INFORMATION; + +typedef struct _JOBOBJECT_BASIC_PROCESS_ID_LIST { + ULONG NumberOfAssignedProcesses; + ULONG NumberOfProcessIdsInList; + ULONG_PTR ProcessIdList[1]; +} JOBOBJECT_BASIC_PROCESS_ID_LIST, *PJOBOBJECT_BASIC_PROCESS_ID_LIST; + +typedef struct _JOBOBJECT_ASSOCIATE_COMPLETION_PORT { + PVOID CompletionKey; + HANDLE CompletionPort; +} JOBOBJECT_ASSOCIATE_COMPLETION_PORT, *PJOBOBJECT_ASSOCIATE_COMPLETION_PORT; + +// // Job Set Array // typedef struct _JOB_SET_ARRAY Index: ntoskrnl/include/internal/ps.h =================================================================== --- ntoskrnl/include/internal/ps.h (revision 70214) +++ ntoskrnl/include/internal/ps.h (working copy) @@ -163,6 +163,13 @@ IN BOOLEAN InJob ); +NTSTATUS +NTAPI +PspAssignProcessToJob( + IN PEPROCESS Process, + IN PEJOB Job +); + // // Security Routines // Index: ntoskrnl/ps/job.c =================================================================== --- ntoskrnl/ps/job.c (revision 70214) +++ ntoskrnl/ps/job.c (working copy) @@ -5,6 +5,7 @@ * PURPOSE: Job Native Functions * PROGRAMMERS: Alex Ionescu (alex@relsoft.net) (stubs) * Thomas Weidenmueller + * Samuel Serapión Vega (encoded@reactos.org) */ /* INCLUDES *****************************************************************/ @@ -61,6 +62,7 @@ if(Job->CompletionPort != NULL) { ObDereferenceObject(Job->CompletionPort); + Job->CompletionPort = NULL; } /* unlink the job object */ @@ -88,8 +90,40 @@ PspAssignProcessToJob(PEPROCESS Process, PEJOB Job) { - DPRINT("PspAssignProcessToJob() is unimplemented!\n"); - return STATUS_NOT_IMPLEMENTED; + DPRINT("PspAssignProcessToJob(%p,%p)\n", Process, Job); + + /* Grab a reference for the lifetime of the process, released in ps/kill.c */ + ObReferenceObject(Job); + + /* Make sure we are not interrupted */ + ExEnterCriticalRegionAndAcquireResourceExclusive(&Job->JobLock); + + /* Assign process to job object */ + InsertTailList(&Job->ProcessListHead, &Process->JobLinks); + + /* Increment counters */ + Job->TotalProcesses++; + Job->ActiveProcesses++; + + if (Job->CompletionPort && Process->UniqueProcessId) + { + /* notify the job object of the new proccess */ + IoSetIoCompletion(Job->CompletionPort, + Job->CompletionKey, + (PVOID)Process->UniqueProcessId, + STATUS_SUCCESS, + JOB_OBJECT_MSG_NEW_PROCESS, + FALSE); + } + + /* FIXME: limit flags not supported */ + if (Job->LimitFlags) + DPRINT1("Unhandled limit flags %x\n", Job->LimitFlags); + + /* resume APCs and release lock */ + ExReleaseResourceAndLeaveCriticalRegion(&Job->JobLock); + + return STATUS_SUCCESS; } NTSTATUS @@ -98,16 +132,50 @@ KPROCESSOR_MODE AccessMode, NTSTATUS ExitStatus ) { - DPRINT("PspTerminateJobObject() is unimplemented!\n"); - return STATUS_NOT_IMPLEMENTED; + PLIST_ENTRY Next; + PEPROCESS Process; + + DPRINT("PspTerminateJobObject(%p,%x,%x)\n", Job, AccessMode, ExitStatus); + + ExEnterCriticalRegionAndAcquireResourceExclusive(&Job->JobLock); + + /* for each process in job process list */ + Next = Job->ProcessListHead.Flink; + while(Next != &Job->ProcessListHead) + { + /* fetch process object */ + Process = (PEPROCESS)(CONTAINING_RECORD(Next, EPROCESS, JobLinks)); + ObReferenceObject(Process); + + /* process might be getting deleted already */ + if (ExAcquireRundownProtection(&Process->RundownProtect)) + { + /* terminate process */ + if (NT_SUCCESS(PsTerminateProcess(Process, ExitStatus))) + { + Job->TotalTerminatedProcesses++; + Job->ActiveProcesses--; + } + ExReleaseRundownProtection(&Process->RundownProtect); + } + + /* say good bye and get next one */ + ObDereferenceObject(Process); + Next = Next->Flink; + } + + /* resume APCs and release lock */ + ExReleaseResourceAndLeaveCriticalRegion(&Job->JobLock); + + return STATUS_SUCCESS; } VOID NTAPI PspRemoveProcessFromJob(IN PEPROCESS Process, - IN PEJOB Job) + IN PEJOB Job) { - /* FIXME */ + /* This function is called as the Process is destroyed, see ps/kill.c */ } VOID @@ -115,11 +183,46 @@ PspExitProcessFromJob(IN PEJOB Job, IN PEPROCESS Process) { - /* FIXME */ + /* This function is called when the last thread exits, see ps/kill.c */ + DPRINT("PspExitProcessFromJob(0x%p, 0x%p)\n", Job, Process); + + /* Make sure we are not interrupted */ + ExEnterCriticalRegionAndAcquireResourceExclusive(&Job->JobLock); + + /* Decrement counters */ + Job->ActiveProcesses--; + ASSERT(Job->ActiveProcesses >= 0); + + if (Job->CompletionPort) + { + /* Notify Job of process exit */ + IoSetIoCompletion(Job->CompletionPort, + Job->CompletionKey, + NULL, + STATUS_SUCCESS, + JOB_OBJECT_MSG_EXIT_PROCESS, + FALSE); + } + + if (Job->ActiveProcesses == 0 && Job->CompletionPort) + { + /* Notify Job that all process have exited */ + IoSetIoCompletion(Job->CompletionPort, + Job->CompletionKey, + NULL, + STATUS_SUCCESS, + JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO, + FALSE); + } + + /* FIXME: Update limits and process stats */ + + /* resume APCs and release lock */ + ExReleaseResourceAndLeaveCriticalRegion(&Job->JobLock); } /* - * @unimplemented + * @implemented */ NTSTATUS NTAPI @@ -132,6 +235,7 @@ NTSTATUS Status; PAGED_CODE(); + DPRINT("NtAssignProcessToJobObject(%x,%x)\n", JobHandle, ProcessHandle); PreviousMode = ExGetPreviousMode(); @@ -150,57 +254,44 @@ NULL); if(NT_SUCCESS(Status)) { - if(Process->Job == NULL) + PEJOB Job; + Status = ObReferenceObjectByHandle( + JobHandle, + JOB_OBJECT_ASSIGN_PROCESS, + PsJobType, + PreviousMode, + (PVOID*)&Job, + NULL); + if(NT_SUCCESS(Status)) { - PEJOB Job; - - Status = ObReferenceObjectByHandle( - JobHandle, - JOB_OBJECT_ASSIGN_PROCESS, - PsJobType, - PreviousMode, - (PVOID*)&Job, - NULL); - if(NT_SUCCESS(Status)) + if (ExAcquireRundownProtection(&Process->RundownProtect)) { - /* lock the process so we can safely assign the process. Note that in the - meanwhile another thread could have assigned this process to a job! */ - - if(ExAcquireRundownProtection(&Process->RundownProtect)) + if (Process->Job == NULL && PsGetProcessSessionId(Process) == Job->SessionId) { - if(Process->Job == NULL && PsGetProcessSessionId(Process) == Job->SessionId) + /* Change the pointer */ + if (InterlockedCompareExchangePointer((PVOID)&Process->Job, Job, NULL)) { - /* Just store the pointer to the job object in the process, we'll - assign it later. The reason we can't do this here is that locking - the job object might require it to wait, which is a bad thing - while holding the process lock! */ - Process->Job = Job; - ObReferenceObject(Job); - } - else - { - /* process is already assigned to a job or session id differs! */ + /* we already had one set, fail */ Status = STATUS_ACCESS_DENIED; } - ExReleaseRundownProtection(&Process->RundownProtect); - - if(NT_SUCCESS(Status)) - { - /* let's actually assign the process to the job as we're not holding - the process lock anymore! */ - Status = PspAssignProcessToJob(Process, Job); - } } - - ObDereferenceObject(Job); + else + { + /* process is already assigned to a job or session id differs! */ + Status = STATUS_ACCESS_DENIED; + } + if (NT_SUCCESS(Status)) + { + Status = PspAssignProcessToJob(Process, Job); + } + ExReleaseRundownProtection(&Process->RundownProtect); } + else + { + Status = STATUS_PROCESS_IS_TERMINATING; + } + ObDereferenceObject(Job); } - else - { - /* process is already assigned to a job or session id differs! */ - Status = STATUS_ACCESS_DENIED; - } - ObDereferenceObject(Process); } @@ -337,6 +428,7 @@ PAGED_CODE(); + DPRINT("NtIsProcessInJob(%x, %x)\n", ProcessHandle, JobHandle); Status = ObReferenceObjectByHandle( ProcessHandle, PROCESS_QUERY_INFORMATION, @@ -346,12 +438,11 @@ NULL); if(NT_SUCCESS(Status)) { - /* FIXME - make sure the job object doesn't get exchanged or deleted while trying to - reference it, e.g. by locking it somehow until it is referenced... */ + /* Lock the process */ + KeEnterCriticalRegion(); + ExAcquirePushLockExclusive(&Process->ProcessLock); - PEJOB ProcessJob = Process->Job; - - if(ProcessJob != NULL) + if(Process->Job != NULL) { if(JobHandle == NULL) { @@ -371,7 +462,7 @@ NULL); if(NT_SUCCESS(Status)) { - Status = ((ProcessJob == JobObject) ? STATUS_PROCESS_IN_JOB : STATUS_PROCESS_NOT_IN_JOB); + Status = ((Process->Job == JobObject) ? STATUS_PROCESS_IN_JOB : STATUS_PROCESS_NOT_IN_JOB); ObDereferenceObject(JobObject); } } @@ -381,6 +472,8 @@ /* the process is not assigned to any job */ Status = STATUS_PROCESS_NOT_IN_JOB; } + ExReleasePushLockExclusive(&Process->ProcessLock); + KeLeaveCriticalRegion(); ObDereferenceObject(Process); } @@ -445,7 +538,7 @@ /* - * @unimplemented + * @implemented */ NTSTATUS NTAPI @@ -456,13 +549,177 @@ ULONG JobInformationLength, PULONG ReturnLength ) { - UNIMPLEMENTED; - return STATUS_NOT_IMPLEMENTED; + PEJOB JobObject; + NTSTATUS Status; + ULONG Length; + KPROCESSOR_MODE PreviousMode = ExGetPreviousMode(); + + PAGED_CODE(); + + DPRINT("NtQueryInformationJobObject(%x,%x,%p,%d)\n", JobHandle, JobInformationClass, JobInformation, JobInformationLength); + + /* Check for user-mode caller */ + if (PreviousMode != KernelMode) + { + /* Prepare to probe parameters */ + _SEH2_TRY + { + /* Probe the buffer */ + ProbeForWrite(JobInformation, + JobInformationLength, + sizeof(ULONG)); + + /* Probe the return length if required */ + if (ReturnLength) ProbeForWriteUlong(ReturnLength); + } + _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) + { + /* Return the exception code */ + _SEH2_YIELD(return _SEH2_GetExceptionCode()); + } + _SEH2_END; + } + + /* Grab a reference to Job */ + Status = ObReferenceObjectByHandle(JobHandle, + JOB_OBJECT_QUERY, + PsJobType, + PreviousMode, + (PVOID*)&JobObject, + NULL); + + if (!NT_SUCCESS(Status)) + return Status; + + /* protect the job from changes while we query it */ + ExEnterCriticalRegionAndAcquireResourceExclusive(&JobObject->JobLock); + + switch (JobInformationClass) + { + case JobObjectBasicLimitInformation: + case JobObjectExtendedLimitInformation: + { + PJOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimit = (PJOBOBJECT_BASIC_LIMIT_INFORMATION)JobInformation; + PJOBOBJECT_EXTENDED_LIMIT_INFORMATION ExtendedLimit = (PJOBOBJECT_EXTENDED_LIMIT_INFORMATION)JobInformation; + + if (JobInformationClass == JobObjectBasicLimitInformation) + { + /* Set the length required and validate it */ + Length = sizeof(JOBOBJECT_BASIC_LIMIT_INFORMATION); + if (JobInformationLength != Length) + { + Status = STATUS_INFO_LENGTH_MISMATCH; + break; + } + } + else if (JobInformationClass == JobObjectExtendedLimitInformation) + { + /* Set the length required and validate it */ + Length = sizeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION); + if (JobInformationLength != Length) + { + Status = STATUS_INFO_LENGTH_MISMATCH; + break; + } + + /* copy extended limit field */ + ExtendedLimit->ProcessMemoryLimit = JobObject->ProcessMemoryLimit; + ExtendedLimit->JobMemoryLimit = JobObject->JobMemoryLimit; + ExtendedLimit->PeakProcessMemoryUsed = JobObject->PeakProcessMemoryUsed; + ExtendedLimit->PeakJobMemoryUsed = JobObject->PeakJobMemoryUsed; + } + + /* Copy basic limit fields */ + BasicLimit->PerProcessUserTimeLimit = JobObject->PerProcessUserTimeLimit; + BasicLimit->PerJobUserTimeLimit = JobObject->PerJobUserTimeLimit; + BasicLimit->LimitFlags = JobObject->LimitFlags; + BasicLimit->MinimumWorkingSetSize = JobObject->MinimumWorkingSetSize; + BasicLimit->MaximumWorkingSetSize = JobObject->MaximumWorkingSetSize; + BasicLimit->ActiveProcessLimit = JobObject->ActiveProcessLimit; + BasicLimit->Affinity = JobObject->Affinity; + BasicLimit->PriorityClass = JobObject->PriorityClass; + BasicLimit->SchedulingClass = JobObject->SchedulingClass; + break; + } + case JobObjectBasicProcessIdList: + { + PJOBOBJECT_BASIC_PROCESS_ID_LIST ProcIdList = (PJOBOBJECT_BASIC_PROCESS_ID_LIST)JobInformation; + PULONG_PTR IdListArray = &ProcIdList->ProcessIdList[0]; + PLIST_ENTRY Next = JobObject->ProcessListHead.Flink; + ULONG ListLength = JobInformationLength - FIELD_OFFSET(JOBOBJECT_BASIC_PROCESS_ID_LIST, ProcessIdList); + + /* This info param needs enough space for the list */ + Length = sizeof(JOBOBJECT_BASIC_PROCESS_ID_LIST); + if (JobInformationLength < Length) + { + /* minimum requeried is the structure */ + Status = STATUS_INFO_LENGTH_MISMATCH; + break; + } + + ProcIdList->NumberOfAssignedProcesses = JobObject->ActiveProcesses; + ProcIdList->NumberOfProcessIdsInList = 0; + + while (Next != &JobObject->ProcessListHead) + { + PEPROCESS Process = (PEPROCESS)CONTAINING_RECORD(Next, EPROCESS, JobLinks); + + /* does another ULONG_PTR fit in the structure? */ + if (ListLength >= sizeof(ULONG_PTR)) + { + if (ExAcquireRundownProtection(&Process->RundownProtect)) + { + *IdListArray++ = (ULONG_PTR)Process->UniqueProcessId; + ListLength -= sizeof(ULONG_PTR); + ProcIdList->NumberOfProcessIdsInList++; + ExReleaseRundownProtection(&Process->RundownProtect); + } + } + else + { + Status = STATUS_BUFFER_OVERFLOW; + Length = ListLength; + break; + } + Next = Next->Flink; + } + + /* we returned this ammount of bytes */ + Length = JobInformationLength - ListLength; + break; + } + default: + { + DPRINT1("Unhandled info class 0x%x\n", JobInformationClass); + Status = STATUS_NOT_IMPLEMENTED; + } + } + + /* resume APCs and release lock */ + ExReleaseResourceAndLeaveCriticalRegion(&JobObject->JobLock); + + /* Release reference to Job */ + ObDereferenceObject(JobObject); + + /* Protect write with SEH */ + _SEH2_TRY + { + /* Check if caller wanted return length */ + if ((ReturnLength) && (Length)) *ReturnLength = Length; + } + _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) + { + /* Get exception code */ + Status = _SEH2_GetExceptionCode(); + } + _SEH2_END; + + return Status; } /* - * @unimplemented + * @implemented */ NTSTATUS NTAPI @@ -472,13 +729,113 @@ PVOID JobInformation, ULONG JobInformationLength) { - UNIMPLEMENTED; - return STATUS_NOT_IMPLEMENTED; + NTSTATUS Status; + PEJOB JobObject; + PVOID IoCompletionPtr; + KPROCESSOR_MODE PreviousMode; + + PAGED_CODE(); + PreviousMode = KeGetPreviousMode(); + + DPRINT("NtSetInformationJobObject(%x,%x,%p,%d)\n", JobHandle, JobInformationClass, JobInformation, JobInformationLength); + + /* Grab a reference to Job */ + Status = ObReferenceObjectByHandle(JobHandle, + JOB_OBJECT_SET_ATTRIBUTES, + PsJobType, + PreviousMode, + (PVOID*)&JobObject, + NULL); + + if (!NT_SUCCESS(Status)) return Status; + + ExEnterCriticalRegionAndAcquireResourceExclusive(&JobObject->JobLock); + + switch (JobInformationClass) + { + case JobObjectAssociateCompletionPortInformation: + { + JOBOBJECT_ASSOCIATE_COMPLETION_PORT CompletionPortInfo; + _SEH2_TRY + { + CompletionPortInfo = *(PJOBOBJECT_ASSOCIATE_COMPLETION_PORT)JobInformation; + } + _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) + { + /* Get the exception code */ + Status = _SEH2_GetExceptionCode(); + _SEH2_YIELD(break); + } + _SEH2_END; + + if (JobInformationLength != sizeof(JOBOBJECT_ASSOCIATE_COMPLETION_PORT)) + { + Status = STATUS_INFO_LENGTH_MISMATCH; + break; + } + + if (!JobObject->CompletionPort && CompletionPortInfo.CompletionPort) + { + /* Get the new completion port object */ + Status = ObReferenceObjectByHandle(CompletionPortInfo.CompletionPort, + IO_COMPLETION_MODIFY_STATE, + IoCompletionType, + PreviousMode, + &IoCompletionPtr, + NULL); + if (NT_SUCCESS(Status)) + { + JobObject->CompletionPort = IoCompletionPtr; + JobObject->CompletionKey = CompletionPortInfo.CompletionKey; + } + } + break; + } + case JobObjectExtendedLimitInformation: + { + JOBOBJECT_EXTENDED_LIMIT_INFORMATION ExtendedLimit; + _SEH2_TRY + { + ExtendedLimit = *(PJOBOBJECT_EXTENDED_LIMIT_INFORMATION)JobInformation; + } + _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) + { + /* Get the exception code */ + Status = _SEH2_GetExceptionCode(); + _SEH2_YIELD(break); + } + _SEH2_END; + + /* FIXME: Quota limits, Time Limits */ + + /* FIXME: No limit flags supported, should detect invalid flags and return failure */ + if (ExtendedLimit.BasicLimitInformation.LimitFlags) + { + Status = STATUS_INVALID_PARAMETER; + } + else + { + JobObject->LimitFlags = ExtendedLimit.BasicLimitInformation.LimitFlags; + } + break; + } + default: + DPRINT1("Unhandled info class 0x%x\n", JobInformationClass); + Status = STATUS_NOT_IMPLEMENTED; + } + + /* resume APCs and release lock */ + ExReleaseResourceAndLeaveCriticalRegion(&JobObject->JobLock); + + /* Release reference to Job */ + ObDereferenceObject(JobObject); + + return Status; } /* - * @unimplemented + * @implemented */ NTSTATUS NTAPI @@ -492,6 +849,7 @@ PAGED_CODE(); + DPRINT("NtTerminateJobObject(%x,%x)\n", JobHandle, ExitStatus); PreviousMode = ExGetPreviousMode(); Status = ObReferenceObjectByHandle( Index: ntoskrnl/ps/process.c =================================================================== --- ntoskrnl/ps/process.c (revision 70214) +++ ntoskrnl/ps/process.c (working copy) @@ -712,8 +712,7 @@ /* Check if the parent had a job */ if ((Parent) && (Parent->Job)) { - /* FIXME: We need to insert this process */ - DPRINT1("Jobs not yet supported\n"); + PspAssignProcessToJob(Process, Parent->Job); } /* Create PEB only for User-Mode Processes */