server: Support nested jobs.

Signed-off-by: Paul Gofman <pgofman@codeweavers.com>
Signed-off-by: Alexandre Julliard <julliard@winehq.org>
This commit is contained in:
Paul Gofman 2021-05-18 14:36:33 +03:00 committed by Alexandre Julliard
parent d7ce5bddf9
commit 21f5597de4
2 changed files with 280 additions and 39 deletions

View File

@ -71,6 +71,7 @@ static BOOL (WINAPI *pQueryFullProcessImageNameA)(HANDLE hProcess, DWORD dwFla
static BOOL (WINAPI *pQueryFullProcessImageNameW)(HANDLE hProcess, DWORD dwFlags, LPWSTR lpExeName, PDWORD lpdwSize);
static DWORD (WINAPI *pK32GetProcessImageFileNameA)(HANDLE,LPSTR,DWORD);
static HANDLE (WINAPI *pCreateJobObjectW)(LPSECURITY_ATTRIBUTES sa, LPCWSTR name);
static HANDLE (WINAPI *pOpenJobObjectA)(DWORD access, BOOL inherit, LPCSTR name);
static BOOL (WINAPI *pAssignProcessToJobObject)(HANDLE job, HANDLE process);
static BOOL (WINAPI *pIsProcessInJob)(HANDLE process, HANDLE job, PBOOL result);
static BOOL (WINAPI *pTerminateJobObject)(HANDLE job, UINT exit_code);
@ -257,6 +258,7 @@ static BOOL init(void)
pQueryFullProcessImageNameW = (void *) GetProcAddress(hkernel32, "QueryFullProcessImageNameW");
pK32GetProcessImageFileNameA = (void *) GetProcAddress(hkernel32, "K32GetProcessImageFileNameA");
pCreateJobObjectW = (void *)GetProcAddress(hkernel32, "CreateJobObjectW");
pOpenJobObjectA = (void *)GetProcAddress(hkernel32, "OpenJobObjectA");
pAssignProcessToJobObject = (void *)GetProcAddress(hkernel32, "AssignProcessToJobObject");
pIsProcessInJob = (void *)GetProcAddress(hkernel32, "IsProcessInJob");
pTerminateJobObject = (void *)GetProcAddress(hkernel32, "TerminateJobObject");
@ -2976,13 +2978,14 @@ static void test_jobInheritance(HANDLE job)
wait_and_close_child_process(&pi);
}
static void test_BreakawayOk(HANDLE job)
static void test_BreakawayOk(HANDLE parent_job)
{
JOBOBJECT_EXTENDED_LIMIT_INFORMATION limit_info;
PROCESS_INFORMATION pi;
STARTUPINFOA si = {0};
char buffer[MAX_PATH + 23];
BOOL ret, out;
BOOL ret, out, nested_jobs;
HANDLE job;
if (!pIsProcessInJob)
{
@ -2990,6 +2993,16 @@ static void test_BreakawayOk(HANDLE job)
return;
}
job = pCreateJobObjectW(NULL, NULL);
ok(!!job, "CreateJobObjectW error %u\n", GetLastError());
ret = pAssignProcessToJobObject(job, GetCurrentProcess());
ok(ret || broken(!ret && GetLastError() == ERROR_ACCESS_DENIED) /* before Win 8. */,
"AssignProcessToJobObject error %u\n", GetLastError());
nested_jobs = ret;
if (!ret)
win_skip("Nested jobs are not supported.\n");
sprintf(buffer, "\"%s\" process exit", selfname);
ret = CreateProcessA(NULL, buffer, NULL, NULL, FALSE, CREATE_BREAKAWAY_FROM_JOB, NULL, NULL, &si, &pi);
ok(!ret, "CreateProcessA expected failure\n");
@ -3001,8 +3014,30 @@ static void test_BreakawayOk(HANDLE job)
wait_and_close_child_process(&pi);
}
if (nested_jobs)
{
limit_info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_BREAKAWAY_OK;
ret = pSetInformationJobObject(job, JobObjectExtendedLimitInformation, &limit_info, sizeof(limit_info));
ok(ret, "SetInformationJobObject error %u\n", GetLastError());
sprintf(buffer, "\"%s\" process exit", selfname);
ret = CreateProcessA(NULL, buffer, NULL, NULL, FALSE, CREATE_BREAKAWAY_FROM_JOB, NULL, NULL, &si, &pi);
ok(ret, "CreateProcessA error %u\n", GetLastError());
ret = pIsProcessInJob(pi.hProcess, job, &out);
ok(ret, "IsProcessInJob error %u\n", GetLastError());
ok(!out, "IsProcessInJob returned out=%u\n", out);
ret = pIsProcessInJob(pi.hProcess, parent_job, &out);
ok(ret, "IsProcessInJob error %u\n", GetLastError());
ok(out, "IsProcessInJob returned out=%u\n", out);
TerminateProcess(pi.hProcess, 0);
wait_and_close_child_process(&pi);
}
limit_info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_BREAKAWAY_OK;
ret = pSetInformationJobObject(job, JobObjectExtendedLimitInformation, &limit_info, sizeof(limit_info));
ret = pSetInformationJobObject(parent_job, JobObjectExtendedLimitInformation, &limit_info, sizeof(limit_info));
ok(ret, "SetInformationJobObject error %u\n", GetLastError());
ret = CreateProcessA(NULL, buffer, NULL, NULL, FALSE, CREATE_BREAKAWAY_FROM_JOB, NULL, NULL, &si, &pi);
@ -3012,6 +3047,10 @@ static void test_BreakawayOk(HANDLE job)
ok(ret, "IsProcessInJob error %u\n", GetLastError());
ok(!out, "IsProcessInJob returned out=%u\n", out);
ret = pIsProcessInJob(pi.hProcess, parent_job, &out);
ok(ret, "IsProcessInJob error %u\n", GetLastError());
ok(!out, "IsProcessInJob returned out=%u\n", out);
wait_and_close_child_process(&pi);
limit_info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK;
@ -4309,6 +4348,135 @@ static void test_dead_process(void)
CloseHandle(pi.hThread);
}
static void test_nested_jobs_child(unsigned int index)
{
HANDLE job, job_parent, job_other;
PROCESS_INFORMATION pi;
char job_name[32];
BOOL ret, out;
sprintf(job_name, "test_nested_jobs_%u", index);
job = pOpenJobObjectA(JOB_OBJECT_ASSIGN_PROCESS | JOB_OBJECT_SET_ATTRIBUTES | JOB_OBJECT_QUERY
| JOB_OBJECT_TERMINATE, FALSE, job_name);
ok(!!job, "OpenJobObjectA error %u\n", GetLastError());
sprintf(job_name, "test_nested_jobs_%u", !index);
job_other = pOpenJobObjectA(JOB_OBJECT_ASSIGN_PROCESS | JOB_OBJECT_SET_ATTRIBUTES | JOB_OBJECT_QUERY
| JOB_OBJECT_TERMINATE, FALSE, job_name);
ok(!!job_other, "OpenJobObjectA error %u\n", GetLastError());
job_parent = pCreateJobObjectW(NULL, NULL);
ok(!!job_parent, "CreateJobObjectA error %u\n", GetLastError());
ret = pAssignProcessToJobObject(job_parent, GetCurrentProcess());
ok(ret, "AssignProcessToJobObject error %u\n", GetLastError());
create_process("wait", &pi);
ret = pAssignProcessToJobObject(job_parent, pi.hProcess);
ok(ret || broken(!ret && GetLastError() == ERROR_ACCESS_DENIED) /* Supported since Windows 8. */,
"AssignProcessToJobObject error %u\n", GetLastError());
if (!ret)
{
win_skip("Nested jobs are not supported.\n");
goto done;
}
ret = pAssignProcessToJobObject(job, pi.hProcess);
ok(ret, "AssignProcessToJobObject error %u\n", GetLastError());
out = FALSE;
ret = pIsProcessInJob(pi.hProcess, NULL, &out);
ok(ret, "IsProcessInJob error %u\n", GetLastError());
ok(out, "IsProcessInJob returned out=%u\n", out);
out = FALSE;
ret = pIsProcessInJob(pi.hProcess, job, &out);
ok(ret, "IsProcessInJob error %u\n", GetLastError());
ok(out, "IsProcessInJob returned out=%u\n", out);
out = TRUE;
ret = pIsProcessInJob(GetCurrentProcess(), job, &out);
ok(ret, "IsProcessInJob error %u\n", GetLastError());
ok(!out, "IsProcessInJob returned out=%u\n", out);
out = FALSE;
ret = pIsProcessInJob(pi.hProcess, job, &out);
ok(ret, "IsProcessInJob error %u\n", GetLastError());
ok(out, "IsProcessInJob returned out=%u\n", out);
ret = pAssignProcessToJobObject(job, GetCurrentProcess());
ok(ret, "AssignProcessToJobObject error %u\n", GetLastError());
TerminateProcess(pi.hProcess, 0);
wait_child_process(pi.hProcess);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
create_process("wait", &pi);
out = FALSE;
ret = pIsProcessInJob(pi.hProcess, job, &out);
ok(ret, "IsProcessInJob error %u\n", GetLastError());
ok(out, "IsProcessInJob returned out=%u\n", out);
out = FALSE;
ret = pIsProcessInJob(pi.hProcess, job_parent, &out);
ok(ret, "IsProcessInJob error %u\n", GetLastError());
ok(out, "IsProcessInJob returned out=%u\n", out);
if (index)
{
ret = pAssignProcessToJobObject(job_other, GetCurrentProcess());
ok(!ret, "AssignProcessToJobObject succeded\n");
ok(GetLastError() == ERROR_ACCESS_DENIED, "Got unexpected error %u.\n", GetLastError());
}
done:
TerminateProcess(pi.hProcess, 0);
wait_child_process(pi.hProcess);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
CloseHandle(job_parent);
CloseHandle(job);
CloseHandle(job_other);
}
static void test_nested_jobs(void)
{
PROCESS_INFORMATION info[2];
char buffer[MAX_PATH + 26];
STARTUPINFOA si = {0};
HANDLE job1, job2;
unsigned int i;
if (!pIsProcessInJob)
{
win_skip("IsProcessInJob not available.\n");
return;
}
job1 = pCreateJobObjectW(NULL, L"test_nested_jobs_0");
ok(!!job1, "CreateJobObjectW failed, error %u.\n", GetLastError());
job2 = pCreateJobObjectW(NULL, L"test_nested_jobs_1");
ok(!!job2, "CreateJobObjectW failed, error %u.\n", GetLastError());
sprintf(buffer, "\"%s\" process nested_jobs 0", selfname);
ok(CreateProcessA(NULL, buffer, NULL, NULL, FALSE, 0, NULL, NULL, &si, &info[0]),
"CreateProcess failed\n");
wait_child_process(info[0].hProcess);
sprintf(buffer, "\"%s\" process nested_jobs 1", selfname);
ok(CreateProcessA(NULL, buffer, NULL, NULL, FALSE, 0, NULL, NULL, &si, &info[1]),
"CreateProcess failed\n");
wait_child_process(info[1].hProcess);
for (i = 0; i < 2; ++i)
{
CloseHandle(info[i].hProcess);
CloseHandle(info[i].hThread);
}
CloseHandle(job1);
CloseHandle(job2);
}
START_TEST(process)
{
HANDLE job, hproc, h, h2;
@ -4384,6 +4552,11 @@ START_TEST(process)
test_handle_list_attribute(TRUE, h, h2);
return;
}
else if (!strcmp(myARGV[2], "nested_jobs") && myARGC >= 4)
{
test_nested_jobs_child(atoi(myARGV[3]));
return;
}
ok(0, "Unexpected command %s\n", myARGV[2]);
return;
@ -4452,6 +4625,7 @@ START_TEST(process)
test_CompletionPort();
test_KillOnJobClose();
test_WaitForJobObject();
test_nested_jobs();
job = test_AddSelfToJob();
test_jobInheritance(job);
test_BreakawayOk(job);

View File

@ -182,7 +182,7 @@ static void job_destroy( struct object *obj );
struct job
{
struct object obj; /* object header */
struct list process_list; /* list of all processes */
struct list process_list; /* list of processes */
int num_processes; /* count of running processes */
int total_processes; /* count of processes which have been assigned */
unsigned int limit_flags; /* limit flags */
@ -190,6 +190,9 @@ struct job
int signaled; /* job is signaled */
struct completion *completion_port; /* associated completion port */
apc_param_t completion_key; /* key to send with completion messages */
struct job *parent;
struct list parent_job_entry; /* list entry for parent job */
struct list child_job_list; /* list of child jobs */
};
static const struct object_ops job_ops =
@ -227,6 +230,7 @@ static struct job *create_job_object( struct object *root, const struct unicode_
{
/* initialize it if it didn't already exist */
list_init( &job->process_list );
list_init( &job->child_job_list );
job->num_processes = 0;
job->total_processes = 0;
job->limit_flags = 0;
@ -234,6 +238,7 @@ static struct job *create_job_object( struct object *root, const struct unicode_
job->signaled = 0;
job->completion_port = NULL;
job->completion_key = 0;
job->parent = NULL;
}
}
return job;
@ -250,14 +255,59 @@ static void add_job_completion( struct job *job, apc_param_t msg, apc_param_t pi
add_completion( job->completion_port, job->completion_key, pid, STATUS_SUCCESS, msg );
}
static int process_in_job( struct job *job, struct process *process )
{
struct job *j;
LIST_FOR_EACH_ENTRY( j, &job->child_job_list, struct job, parent_job_entry )
{
assert( j->parent == job );
if (process_in_job( j, process )) return 1;
}
return process->job == job;
}
static void add_job_process( struct job *job, struct process *process )
{
struct job *j, *common_parent;
process_id_t pid;
if (job == process->job) return;
if ((common_parent = process->job))
{
if (job->parent)
{
for (j = job->parent; j; j = j->parent)
if (j == common_parent) break;
if (j != common_parent)
{
/* Job already has parent and the process is not in the job's chain. */
set_error( STATUS_ACCESS_DENIED );
return;
}
/* process->job is referenced in the job->parent chain. */
release_object( process->job );
}
else
{
/* transfer reference. */
job->parent = process->job;
list_add_tail( &job->parent->child_job_list, &job->parent_job_entry );
}
list_remove( &process->job_entry );
}
process->job = (struct job *)grab_object( job );
list_add_tail( &job->process_list, &process->job_entry );
job->num_processes++;
job->total_processes++;
add_job_completion( job, JOB_OBJECT_MSG_NEW_PROCESS, get_process_id(process) );
pid = get_process_id( process );
for (j = job; j != common_parent; j = j->parent)
{
j->num_processes++;
j->total_processes++;
add_job_completion( j, JOB_OBJECT_MSG_NEW_PROCESS, pid );
}
}
/* called when a process has terminated, allow one additional process */
@ -265,37 +315,41 @@ static void release_job_process( struct process *process )
{
struct job *job = process->job;
if (!job) return;
while (job)
{
assert( job->num_processes );
job->num_processes--;
assert( job->num_processes );
job->num_processes--;
if (!job->terminating)
add_job_completion( job, JOB_OBJECT_MSG_EXIT_PROCESS, get_process_id(process) );
if (!job->terminating)
add_job_completion( job, JOB_OBJECT_MSG_EXIT_PROCESS, get_process_id(process) );
if (!job->num_processes)
add_job_completion( job, JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO, 0 );
if (!job->num_processes)
add_job_completion( job, JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO, 0 );
job = job->parent;
}
}
static void terminate_job( struct job *job, int exit_code )
{
/* don't report completion events for terminated processes */
job->terminating = 1;
struct process *process, *next_process;
struct job *j, *next_job;
for (;;) /* restart from the beginning of the list every time */
LIST_FOR_EACH_ENTRY_SAFE( j, next_job, &job->child_job_list, struct job, parent_job_entry )
{
struct process *process;
assert( j->parent == job );
/* find the first process associated with this job and still running */
LIST_FOR_EACH_ENTRY( process, &job->process_list, struct process, job_entry )
{
if (process->running_threads) break;
}
if (&process->job_entry == &job->process_list) break; /* no process found */
assert( process->job == job );
terminate_process( process, NULL, exit_code );
grab_object( j );
terminate_job( j, exit_code );
release_object( j );
}
job->terminating = 1;
LIST_FOR_EACH_ENTRY_SAFE( process, next_process, &job->process_list, struct process, job_entry )
{
assert( process->job == job );
if (process->running_threads) terminate_process( process, NULL, exit_code );
}
job->terminating = 0;
job->signaled = 1;
wake_up( &job->obj, 0 );
@ -320,16 +374,23 @@ static void job_destroy( struct object *obj )
assert( obj->ops == &job_ops );
assert( !job->num_processes );
assert( list_empty(&job->process_list) );
assert( list_empty( &job->process_list ));
assert( list_empty( &job->child_job_list ));
if (job->completion_port) release_object( job->completion_port );
if (job->parent)
{
list_remove( &job->parent_job_entry );
release_object( job->parent );
}
}
static void job_dump( struct object *obj, int verbose )
{
struct job *job = (struct job *)obj;
assert( obj->ops == &job_ops );
fprintf( stderr, "Job processes=%d\n", list_count(&job->process_list) );
fprintf( stderr, "Job processes=%d child_jobs=%d parent=%p\n",
list_count(&job->process_list), list_count(&job->child_job_list), job->parent );
}
static int job_signaled( struct object *obj, struct wait_queue_entry *entry )
@ -1026,6 +1087,7 @@ DECL_HANDLER(new_process)
struct thread *parent_thread = current;
int socket_fd = thread_get_inflight_fd( current, req->socket_fd );
const obj_handle_t *handles = NULL;
struct job *job;
if (socket_fd == -1)
{
@ -1062,6 +1124,8 @@ DECL_HANDLER(new_process)
}
else parent = (struct process *)grab_object( current->process );
/* If a job further in the job chain does not permit breakaway process creation
* succeeds and the process which is trying to breakaway is assigned to that job. */
if (parent->job && (req->flags & PROCESS_CREATE_FLAGS_BREAKAWAY) &&
!(parent->job->limit_flags & (JOB_OBJECT_LIMIT_BREAKAWAY_OK | JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK)))
{
@ -1152,11 +1216,18 @@ DECL_HANDLER(new_process)
process->startup_info = (struct startup_info *)grab_object( info );
if (parent->job
&& !(req->flags & PROCESS_CREATE_FLAGS_BREAKAWAY)
&& !(parent->job->limit_flags & JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK))
job = parent->job;
while (job)
{
add_job_process( parent->job, process );
if (!(job->limit_flags & JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK)
&& !(req->flags & PROCESS_CREATE_FLAGS_BREAKAWAY
&& job->limit_flags & JOB_OBJECT_LIMIT_BREAKAWAY_OK))
{
add_job_process( job, process );
assert( !get_error() );
break;
}
job = job->parent;
}
/* connect to the window station */
@ -1570,12 +1641,8 @@ DECL_HANDLER(assign_job)
if ((process = get_process_from_handle( req->process, PROCESS_SET_QUOTA | PROCESS_TERMINATE )))
{
if (!process->running_threads)
set_error( STATUS_PROCESS_IS_TERMINATING );
else if (process->job)
set_error( STATUS_ACCESS_DENIED );
else
add_job_process( job, process );
if (!process->running_threads) set_error( STATUS_PROCESS_IS_TERMINATING );
else add_job_process( job, process );
release_object( process );
}
release_object( job );
@ -1597,7 +1664,7 @@ DECL_HANDLER(process_in_job)
}
else if ((job = get_job_obj( current->process, req->job, JOB_OBJECT_QUERY )))
{
set_error( process->job == job ? STATUS_PROCESS_IN_JOB : STATUS_PROCESS_NOT_IN_JOB );
set_error( process_in_job( job, process ) ? STATUS_PROCESS_IN_JOB : STATUS_PROCESS_NOT_IN_JOB );
release_object( job );
}
release_object( process );