Signed-off-by: Paul Gofman pgofman@codeweavers.com --- v2: - remove a spurious line break. v3: - fix compiler warnings in test messages on 32 bit.
dlls/kernel32/tests/process.c | 181 +++++++++++++++++++++++++++++++++- server/process.c | 148 ++++++++++++++++++++------- 2 files changed, 289 insertions(+), 40 deletions(-)
diff --git a/dlls/kernel32/tests/process.c b/dlls/kernel32/tests/process.c index f960efb05a8..5622a854608 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"); @@ -2976,13 +2979,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 +2994,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 +3015,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 +3048,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 +4349,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 +4553,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 +4626,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 17abd9800d2..8ff4fc51558 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 @@ -182,7 +183,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 +191,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 +231,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 +239,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 +256,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 */ @@ -265,40 +325,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 ) @@ -320,16 +382,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 ) @@ -1021,6 +1090,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) { @@ -1057,6 +1127,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))) { @@ -1147,11 +1219,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 */ @@ -1561,12 +1639,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 ); @@ -1588,7 +1662,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 );
Signed-off-by: Paul Gofman pgofman@codeweavers.com --- v2: - do not send completion for terminating process in add_job_completion_existing_processes(). v3: - fix compiler warnings in test messages on 32 bit.
dlls/kernel32/tests/process.c | 64 +++++++++++++++++++++++++++++++++-- server/process.c | 17 ++++++++++ 2 files changed, 79 insertions(+), 2 deletions(-)
diff --git a/dlls/kernel32/tests/process.c b/dlls/kernel32/tests/process.c index 5622a854608..ce3d1a3b124 100644 --- a/dlls/kernel32/tests/process.c +++ b/dlls/kernel32/tests/process.c @@ -2787,13 +2787,17 @@ static void test_QueryInformationJobObject(void) static void test_CompletionPort(void) { JOBOBJECT_ASSOCIATE_COMPLETION_PORT port_info; - PROCESS_INFORMATION pi; + PROCESS_INFORMATION pi, pi2; HANDLE job, port; BOOL ret;
job = pCreateJobObjectW(NULL, NULL); ok(job != NULL, "CreateJobObject error %u\n", GetLastError());
+ create_process("wait", &pi2); + ret = pAssignProcessToJobObject(job, pi2.hProcess); + ok(ret, "AssignProcessToJobObject error %u\n", GetLastError()); + port = pCreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 1); ok(port != NULL, "CreateIoCompletionPort error %u\n", GetLastError());
@@ -2807,12 +2811,19 @@ static void test_CompletionPort(void) ret = pAssignProcessToJobObject(job, pi.hProcess); ok(ret, "AssignProcessToJobObject error %u\n", GetLastError());
+ test_completion(port, JOB_OBJECT_MSG_NEW_PROCESS, (DWORD_PTR)job, pi2.dwProcessId, 0); test_completion(port, JOB_OBJECT_MSG_NEW_PROCESS, (DWORD_PTR)job, pi.dwProcessId, 0);
TerminateProcess(pi.hProcess, 0); wait_child_process(pi.hProcess);
test_completion(port, JOB_OBJECT_MSG_EXIT_PROCESS, (DWORD_PTR)job, pi.dwProcessId, 0); + TerminateProcess(pi2.hProcess, 0); + wait_child_process(pi2.hProcess); + CloseHandle(pi2.hProcess); + CloseHandle(pi2.hThread); + + test_completion(port, JOB_OBJECT_MSG_EXIT_PROCESS, (DWORD_PTR)job, pi2.dwProcessId, 0); test_completion(port, JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO, (DWORD_PTR)job, 0, 100);
CloseHandle(pi.hProcess); @@ -4351,10 +4362,15 @@ static void test_dead_process(void)
static void test_nested_jobs_child(unsigned int index) { - HANDLE job, job_parent, job_other; + JOBOBJECT_ASSOCIATE_COMPLETION_PORT port_info; + HANDLE job, job_parent, job_other, port; PROCESS_INFORMATION pi; + OVERLAPPED *overlapped; char job_name[32]; + ULONG_PTR value; + DWORD dead_pid; BOOL ret, out; + DWORD key;
sprintf(job_name, "test_nested_jobs_%u", index); job = pOpenJobObjectA(JOB_OBJECT_ASSIGN_PROCESS | JOB_OBJECT_SET_ATTRIBUTES | JOB_OBJECT_QUERY @@ -4413,6 +4429,20 @@ static void test_nested_jobs_child(unsigned int index) CloseHandle(pi.hProcess); CloseHandle(pi.hThread);
+ dead_pid = pi.dwProcessId; + + port = pCreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 1); + ok(!!port, "CreateIoCompletionPort error %u\n", GetLastError()); + + port_info.CompletionPort = port; + port_info.CompletionKey = job; + ret = pSetInformationJobObject(job, JobObjectAssociateCompletionPortInformation, &port_info, sizeof(port_info)); + ok(ret, "SetInformationJobObject error %u\n", GetLastError()); + port_info.CompletionKey = job_parent; + ret = pSetInformationJobObject(job_parent, JobObjectAssociateCompletionPortInformation, + &port_info, sizeof(port_info)); + ok(ret, "SetInformationJobObject error %u\n", GetLastError()); + create_process("wait", &pi); out = FALSE; ret = pIsProcessInJob(pi.hProcess, job, &out); @@ -4424,12 +4454,42 @@ static void test_nested_jobs_child(unsigned int index) ok(ret, "IsProcessInJob error %u\n", GetLastError()); ok(out, "IsProcessInJob returned out=%u\n", out);
+ /* The first already dead child process still shows up randomly. */ + do + { + ret = GetQueuedCompletionStatus(port, &key, &value, &overlapped, 0); + } while (ret && (ULONG_PTR)overlapped == dead_pid); + + ok(ret, "GetQueuedCompletionStatus: %x\n", GetLastError()); + ok(key == JOB_OBJECT_MSG_NEW_PROCESS, "unexpected key %x\n", key); + ok((HANDLE)value == job, "unexpected value %p\n", (void *)value); + ok((ULONG_PTR)overlapped == GetCurrentProcessId(), "unexpected pid %#x\n", (DWORD)(DWORD_PTR)overlapped); + + do + { + ret = GetQueuedCompletionStatus(port, &key, &value, &overlapped, 0); + } while (ret && (ULONG_PTR)overlapped == dead_pid); + + ok(ret, "GetQueuedCompletionStatus: %x\n", GetLastError()); + ok(key == JOB_OBJECT_MSG_NEW_PROCESS, "unexpected key %x\n", key); + ok((HANDLE)value == job_parent, "unexpected value %p\n", (void *)value); + ok((ULONG_PTR)overlapped == GetCurrentProcessId(), "unexpected pid %#x\n", (DWORD)(DWORD_PTR)overlapped); + + test_completion(port, JOB_OBJECT_MSG_NEW_PROCESS, (DWORD_PTR)job, pi.dwProcessId, 0); + test_completion(port, JOB_OBJECT_MSG_NEW_PROCESS, (DWORD_PTR)job_parent, pi.dwProcessId, 0); + + ret = GetQueuedCompletionStatus(port, &key, &value, &overlapped, 0); + ok(!ret, "GetQueuedCompletionStatus succeeded.\n"); + if (index) { ret = pAssignProcessToJobObject(job_other, GetCurrentProcess()); ok(!ret, "AssignProcessToJobObject succeded\n"); ok(GetLastError() == ERROR_ACCESS_DENIED, "Got unexpected error %u.\n", GetLastError()); } + + CloseHandle(port); + done: TerminateProcess(pi.hProcess, 0); wait_child_process(pi.hProcess); diff --git a/server/process.c b/server/process.c index 8ff4fc51558..7b461f96a58 100644 --- a/server/process.c +++ b/server/process.c @@ -256,6 +256,21 @@ 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 add_job_completion_existing_processes( struct job *job, void *param ) +{ + struct job *completion_job = param; + struct process *process; + + assert( completion_job->obj.ops == &job_ops ); + + LIST_FOR_EACH_ENTRY( process, &job->process_list, struct process, job_entry ) + { + if (process->running_threads) + add_job_completion( completion_job, JOB_OBJECT_MSG_NEW_PROCESS, get_process_id( process )); + } + return 0; +} + static int walk_job( struct job *job, void *param, int (*callback)( struct job *, void * )) { struct job *j, *next; @@ -1713,6 +1728,8 @@ DECL_HANDLER(set_job_completion_port) { job->completion_port = get_completion_obj( current->process, req->port, IO_COMPLETION_MODIFY_STATE ); job->completion_key = req->key; + + walk_job( job, job, add_job_completion_existing_processes ); } else set_error( STATUS_INVALID_PARAMETER );
Hi,
While running your changed tests, I think I found new failures. Being a bot and all I'm not very good at pattern recognition, so I might be wrong, but could you please double-check?
Full results can be found at: https://testbot.winehq.org/JobDetails.pl?Key=90359
Your paranoid android.
=== w1064 (32 bit report) ===
kernel32: process.c:4171: Test failed: Got unexpected ret 0x1, GetLastError() 1813. process.c:4185: Test failed: Got unexpected ret 0x1, GetLastError19(8c): p181r3o.ce process.c:4114: Test failed: Got unexpected ret 0, level 2, GetLastError() 6. process.c:4121: Test failed: Got parent id 7400, parent_data.parent_id 0. process.c:4265: Test failed: Unexpected return value, error 203.
=== w1064_tsign (32 bit report) ===
kernel32: process.c:4171: Test failed: Got unexpected ret 0x1, GetLastError() 1813. process.c:4185: Test failed: Got unexpected ret 0x1, GetLastError() 1813. process.c:4114: Test failed: Got unexpected ret 0, level 2, GetLastError() 6. process.c:4121: Test failed: Got parent id 6764, parent_data.parent_id 0.
Hello Francois,
I've been looking at these test failures a bit (which are not related to the patches I sent).
The errors correspond to the situation as if PROC_THREAD_ATTRIBUTE_PARENT_PROCESS process attribute is silently ignored. Which results in both unwanted process creation success (which should fail due to invalid parent process handle) and the attribute not working and thus failing the test responsible for checking the actual parent itself. I could not find any specific privilege responsible for this attribute. Since the error happens on these two machines only (and only on 32 bit), maybe there are some specifics common and unique to these machines which can give a clue why that is not working?
Regards, Paul.
On 5/13/21 15:22, Marvin wrote:
Hi,
While running your changed tests, I think I found new failures. Being a bot and all I'm not very good at pattern recognition, so I might be wrong, but could you please double-check?
Full results can be found at: https://testbot.winehq.org/JobDetails.pl?Key=90359
Your paranoid android.
=== w1064 (32 bit report) ===
kernel32: process.c:4171: Test failed: Got unexpected ret 0x1, GetLastError() 1813. process.c:4185: Test failed: Got unexpected ret 0x1, GetLastError19(8c): p181r3o.ce process.c:4114: Test failed: Got unexpected ret 0, level 2, GetLastError() 6. process.c:4121: Test failed: Got parent id 7400, parent_data.parent_id 0. process.c:4265: Test failed: Unexpected return value, error 203.
=== w1064_tsign (32 bit report) ===
kernel32: process.c:4171: Test failed: Got unexpected ret 0x1, GetLastError() 1813. process.c:4185: Test failed: Got unexpected ret 0x1, GetLastError() 1813. process.c:4114: Test failed: Got unexpected ret 0, level 2, GetLastError() 6. process.c:4121: Test failed: Got parent id 6764, parent_data.parent_id 0.
Hi,
While running your changed tests, I think I found new failures. Being a bot and all I'm not very good at pattern recognition, so I might be wrong, but could you please double-check?
Full results can be found at: https://testbot.winehq.org/JobDetails.pl?Key=90358
Your paranoid android.
=== w1064 (32 bit report) ===
kernel32: process.c:4160: Test failed: Got unexpected ret 0x1, GetLastError() 1813. process.c:4174: Test failed: Got unexpected ret 0x1, GetLastError() 1813. process.c:4103: Test failed: Got unexpected ret 0, level 2, GetLastError() 6. process.c:4110: Test failed: Got parent id 7472, parent_data.parent_id 0. process.c:4254: Test failed: Unexpected return value, error 203.
=== w1064_tsign (32 bit report) ===
kernel32: process.c:4160: Test failed: Got unexpected ret 0x1, GetLastError() 1813. process.c:4174: Test failed: Got unexpected ret 0x1, GetLastError() 1813. process.c:4103: Test failed: Got unexpected ret 0, level 2, GetLastError() 6. process.c:4110: Test failed: Got parent id 7936, parent_data.parent_id 0.