[PATCH v3 1/2] server: Support nested jobs.

Paul Gofman pgofman at codeweavers.com
Thu May 13 07:06:29 CDT 2021


Signed-off-by: Paul Gofman <pgofman at 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 );
-- 
2.31.1




More information about the wine-devel mailing list