Signed-off-by: Paul Gofman pgofman@codeweavers.com --- dlls/kernel32/tests/process.c | 182 +++++++++++++++++++++++++++++++++- server/process.c | 148 ++++++++++++++++++++------- 2 files changed, 290 insertions(+), 40 deletions(-)
diff --git a/dlls/kernel32/tests/process.c b/dlls/kernel32/tests/process.c index f960efb05a8..18f0415cb04 100644 --- a/dlls/kernel32/tests/process.c +++ b/dlls/kernel32/tests/process.c @@ -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,8 @@ 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"); @@ -2810,6 +2813,7 @@ static void test_CompletionPort(void) wait_child_process(pi.hProcess);
test_completion(port, JOB_OBJECT_MSG_EXIT_PROCESS, (DWORD_PTR)job, pi.dwProcessId, 0); + test_completion(port, JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO, (DWORD_PTR)job, 0, 100);
CloseHandle(pi.hProcess); @@ -2976,13 +2980,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 +2995,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 +3016,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 +3049,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 +4350,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", GetLastError()); + 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 +4554,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 +4627,7 @@ START_TEST(process) test_CompletionPort(); test_KillOnJobClose(); test_WaitForJobObject(); + test_nested_jobs(); job = test_AddSelfToJob(); test_jobInheritance(job); test_BreakawayOk(job); diff --git a/server/process.c b/server/process.c index 5ef7596733f..8b6d6fd3e9b 100644 --- a/server/process.c +++ b/server/process.c @@ -30,6 +30,7 @@ #include <stdio.h> #include <stdlib.h> #include <sys/time.h> +#include <stdint.h> #ifdef HAVE_SYS_SOCKET_H # include <sys/socket.h> #endif @@ -183,7 +184,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 */ @@ -191,6 +192,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 = @@ -228,6 +232,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; @@ -235,6 +240,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; @@ -251,14 +257,68 @@ 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 walk_job( struct job *job, void *param, int (*callback)( struct job *, void * )) +{ + struct job *j, *next; + + LIST_FOR_EACH_ENTRY_SAFE( j, next, &job->child_job_list, struct job, parent_job_entry ) + { + assert( j->parent == job ); + if (walk_job( j, param, callback )) return 1; + } + return callback( job, param ); +} + +static int process_in_job( struct job *job, void *param ) +{ + struct process *process = param; + + assert( process->obj.ops == &process_ops ); + return process->job == job; +} + static void add_job_process( struct job *job, struct process *process ) { + process_id_t pid; + struct job *j; + + if (job == process->job) return; + + if (process->job) + { + list_remove( &process->job_entry ); + if (job->parent) + { + j = job->parent; + while (j && j != process->job) + j = j->parent; + + if (!j) + { + set_error( STATUS_ACCESS_DENIED ); + return; + } + release_object( process->job ); + } + else + { + job->parent = process->job; + list_add_tail( &job->parent->child_job_list, &job->parent_job_entry ); + } + } + + pid = get_process_id( process ); + j = job; + while (j != process->job) + { + j->num_processes++; + j->total_processes++; + add_job_completion( j, JOB_OBJECT_MSG_NEW_PROCESS, pid ); + j = j->parent; + } + 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) ); }
/* called when a process has terminated, allow one additional process */ @@ -266,40 +326,42 @@ 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 ) +static int terminate_job_processes( struct job *job, void *exit_code ) { + struct process *process, *next; + /* don't report completion events for terminated processes */ job->terminating = 1;
- for (;;) /* restart from the beginning of the list every time */ + LIST_FOR_EACH_ENTRY_SAFE( process, next, &job->process_list, struct process, job_entry ) { - struct process *process; - - /* 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 ); + if (process->running_threads) terminate_process( process, NULL, (uintptr_t)exit_code ); } - job->terminating = 0; job->signaled = 1; wake_up( &job->obj, 0 ); + return 0; +} + +static void terminate_job( struct job *job, int exit_code ) +{ + walk_job( job, (void *)(uintptr_t)exit_code, terminate_job_processes ); }
static int job_close_handle( struct object *obj, struct process *process, obj_handle_t handle ) @@ -321,16 +383,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 ) @@ -1025,6 +1094,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) { @@ -1061,6 +1131,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))) { @@ -1151,11 +1223,17 @@ 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 ); + break; + } + job = job->parent; }
/* connect to the window station */ @@ -1570,12 +1648,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 +1671,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( walk_job( job, process, process_in_job ) ? STATUS_PROCESS_IN_JOB : STATUS_PROCESS_NOT_IN_JOB ); release_object( job ); } release_object( process );