Signed-off-by: Paul Gofman <pgofman(a)codeweavers.com>
---
v4:
- get rid of walk_job() and use recursive helpers instead;
- make sure job parameter for terminate_job() always has at least one
reference during function call;
- simplify add_job_process() a bit;
- don't remove process from job's process list on error in add_job_process().
dlls/kernel32/tests/process.c | 180 +++++++++++++++++++++++++++++++++-
server/process.c | 139 +++++++++++++++++++-------
2 files changed, 280 insertions(+), 39 deletions(-)
diff --git a/dlls/kernel32/tests/process.c b/dlls/kernel32/tests/process.c
index 7d5d2ead75b..f170043b5b9 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,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);
diff --git a/server/process.c b/server/process.c
index aab723114a8..8baccdbad94 100644
--- a/server/process.c
+++ b/server/process.c
@@ -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 );
--
2.31.1