diff --git a/debian/changelog b/debian/changelog index 1fa695d9..c2d23396 100644 --- a/debian/changelog +++ b/debian/changelog @@ -14,6 +14,7 @@ wine-staging (1.7.38) UNRELEASED; urgency=low * Added patch for stub of gdiplus.GdipCreateEffect. * Added patches for ntoskrnl.ProbeFor{Read,Write}. * Added patch for support of shell32 file operation progress dialog. + * Added patch for basic implementation of job objects. * Removed patch to properly call DriverUnload when unloading device drivers (accepted upstream). * Removed patch to allow Accept-Encoding for HTTP/1.0 in wininet (accepted upstream). * Removed patch to declare pDirectInputCreateEx in a MSVC compatible way (accepted upstream). diff --git a/patches/kernel32-JobObjects/definition b/patches/kernel32-JobObjects/definition deleted file mode 100644 index 2c37f129..00000000 --- a/patches/kernel32-JobObjects/definition +++ /dev/null @@ -1 +0,0 @@ -Depends: kernel32-Console_Handles diff --git a/patches/patchinstall.sh b/patches/patchinstall.sh index 044386d6..69462e72 100755 --- a/patches/patchinstall.sh +++ b/patches/patchinstall.sh @@ -103,7 +103,6 @@ patch_enable_all () enable_kernel32_GetStringTypeW="$1" enable_kernel32_GetSystemTimes="$1" enable_kernel32_GetVolumePathName="$1" - enable_kernel32_JobObjects="$1" enable_kernel32_Named_Pipe="$1" enable_kernel32_NeedCurrentDirectoryForExePath="$1" enable_kernel32_Profile="$1" @@ -157,6 +156,7 @@ patch_enable_all () enable_server_ClipCursor="$1" enable_server_CreateProcess_ACLs="$1" enable_server_Inherited_ACLs="$1" + enable_server_JobObjects="$1" enable_server_Key_State="$1" enable_server_Misc_ACL="$1" enable_server_OpenProcess="$1" @@ -341,9 +341,6 @@ patch_enable () kernel32-GetVolumePathName) enable_kernel32_GetVolumePathName="$2" ;; - kernel32-JobObjects) - enable_kernel32_JobObjects="$2" - ;; kernel32-Named_Pipe) enable_kernel32_Named_Pipe="$2" ;; @@ -503,6 +500,9 @@ patch_enable () server-Inherited_ACLs) enable_server_Inherited_ACLs="$2" ;; + server-JobObjects) + enable_server_JobObjects="$2" + ;; server-Key_State) enable_server_Key_State="$2" ;; @@ -899,6 +899,21 @@ if test "$enable_shell32_Progress_Dialog" -eq 1; then enable_kernel32_CopyFileEx=1 fi +if test "$enable_server_JobObjects" -eq 1; then + if test "$enable_kernel32_Console_Handles" -gt 1; then + abort "Patchset kernel32-Console_Handles disabled, but server-JobObjects depends on that." + fi + if test "$enable_server_Misc_ACL" -gt 1; then + abort "Patchset server-Misc_ACL disabled, but server-JobObjects depends on that." + fi + if test "$enable_server_OpenProcess" -gt 1; then + abort "Patchset server-OpenProcess disabled, but server-JobObjects depends on that." + fi + enable_kernel32_Console_Handles=1 + enable_server_Misc_ACL=1 + enable_server_OpenProcess=1 +fi + if test "$enable_server_ACL_Compat" -eq 1; then if test "$enable_server_Inherited_ACLs" -gt 1; then abort "Patchset server-Inherited_ACLs disabled, but server-ACL_Compat depends on that." @@ -966,13 +981,6 @@ if test "$enable_ntdll_Junction_Points" -eq 1; then enable_ntdll_Fix_Free=1 fi -if test "$enable_kernel32_JobObjects" -eq 1; then - if test "$enable_kernel32_Console_Handles" -gt 1; then - abort "Patchset kernel32-Console_Handles disabled, but kernel32-JobObjects depends on that." - fi - enable_kernel32_Console_Handles=1 -fi - if test "$enable_kernel32_CopyFileEx" -eq 1; then if test "$enable_kernel32_SetFileInformationByHandle" -gt 1; then abort "Patchset kernel32-SetFileInformationByHandle disabled, but kernel32-CopyFileEx depends on that." @@ -1538,18 +1546,6 @@ if test "$enable_dxgi_GetDesc" -eq 1; then ) >> "$patchlist" fi -# Patchset makedep-PARENTSPEC -# | -# | Modified files: -# | * tools/makedep.c -# | -if test "$enable_makedep_PARENTSPEC" -eq 1; then - patch_apply makedep-PARENTSPEC/0001-makedep-Add-support-for-PARENTSPEC-Makefile-variable.patch - ( - echo '+ { "Sebastian Lackner", "makedep: Add support for PARENTSPEC Makefile variable.", 1 },'; - ) >> "$patchlist" -fi - # Patchset ntdll-DllRedirects # | # | Modified files: @@ -1570,6 +1566,18 @@ if test "$enable_ntdll_DllRedirects" -eq 1; then ) >> "$patchlist" fi +# Patchset makedep-PARENTSPEC +# | +# | Modified files: +# | * tools/makedep.c +# | +if test "$enable_makedep_PARENTSPEC" -eq 1; then + patch_apply makedep-PARENTSPEC/0001-makedep-Add-support-for-PARENTSPEC-Makefile-variable.patch + ( + echo '+ { "Sebastian Lackner", "makedep: Add support for PARENTSPEC Makefile variable.", 1 },'; + ) >> "$patchlist" +fi + # Patchset wined3d-CSMT_Helper # | # | Modified files: @@ -2311,34 +2319,6 @@ if test "$enable_kernel32_GetVolumePathName" -eq 1; then ) >> "$patchlist" fi -# Patchset kernel32-JobObjects -# | -# | Modified files: -# | * dlls/kernel32/tests/process.c -# | -if test "$enable_kernel32_JobObjects" -eq 1; then - patch_apply kernel32-JobObjects/0001-kernel32-tests-Allow-multiple-subprocess-commands-in.patch - patch_apply kernel32-JobObjects/0002-kernel32-tests-Add-tests-for-IsProcessInJob.patch - patch_apply kernel32-JobObjects/0003-kernel32-tests-Add-tests-for-TerminateJobObject.patch - patch_apply kernel32-JobObjects/0004-kernel32-tests-Add-tests-for-QueryInformationJobObje.patch - patch_apply kernel32-JobObjects/0005-kernel32-tests-Add-tests-for-job-object-completion-p.patch - patch_apply kernel32-JobObjects/0006-kernel32-tests-Add-tests-for-JOB_OBJECT_LIMIT_KILL_O.patch - patch_apply kernel32-JobObjects/0007-kernel32-tests-Add-tests-for-JOB_OBJECT_LIMIT_ACTIVE.patch - patch_apply kernel32-JobObjects/0008-kernel32-tests-Add-tests-for-JOB_OBJECT_LIMIT_BREAKA.patch - patch_apply kernel32-JobObjects/0009-kernel32-tests-Add-tests-for-job-inheritance.patch - ( - echo '+ { "Sebastian Lackner", "kernel32/tests: Allow multiple subprocess commands in process tests.", 1 },'; - echo '+ { "Andrew Cook", "kernel32/tests: Add tests for IsProcessInJob.", 1 },'; - echo '+ { "Andrew Cook", "kernel32/tests: Add tests for TerminateJobObject.", 1 },'; - echo '+ { "Andrew Cook", "kernel32/tests: Add tests for QueryInformationJobObject.", 1 },'; - echo '+ { "Andrew Cook", "kernel32/tests: Add tests for job object completion ports.", 1 },'; - echo '+ { "Andrew Cook", "kernel32/tests: Add tests for JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE.", 1 },'; - echo '+ { "Andrew Cook", "kernel32/tests: Add tests for JOB_OBJECT_LIMIT_ACTIVE_PROCESS.", 1 },'; - echo '+ { "Andrew Cook", "kernel32/tests: Add tests for JOB_OBJECT_LIMIT_BREAKAWAY_OK.", 1 },'; - echo '+ { "Andrew Cook", "kernel32/tests: Add tests for job inheritance.", 1 },'; - ) >> "$patchlist" -fi - # Patchset kernel32-Named_Pipe # | # | This patchset fixes the following Wine bugs: @@ -3285,18 +3265,18 @@ if test "$enable_server_CreateProcess_ACLs" -eq 1; then ) >> "$patchlist" fi -# Patchset server-Key_State +# Patchset server-OpenProcess # | # | This patchset fixes the following Wine bugs: -# | * [#27238] Fallback to global key state for threads without a queue +# | * [#37087] Return an error when trying to open a terminated process # | # | Modified files: -# | * server/queue.c +# | * server/process.c, server/process.h # | -if test "$enable_server_Key_State" -eq 1; then - patch_apply server-Key_State/0001-server-Fall-back-to-global-key-state-when-thread-doe.patch +if test "$enable_server_OpenProcess" -eq 1; then + patch_apply server-OpenProcess/0001-server-Return-error-when-opening-a-terminating-proce.patch ( - echo '+ { "Sebastian Lackner", "server: Fall back to global key state when thread doesn'\''t have a queue.", 1 },'; + echo '+ { "Michael Müller", "server: Return error when opening a terminating process.", 3 },'; ) >> "$patchlist" fi @@ -3317,18 +3297,49 @@ if test "$enable_server_Misc_ACL" -eq 1; then ) >> "$patchlist" fi -# Patchset server-OpenProcess -# | -# | This patchset fixes the following Wine bugs: -# | * [#37087] Return an error when trying to open a terminated process +# Patchset server-JobObjects # | # | Modified files: -# | * server/process.c, server/process.h +# | * dlls/kernel32/tests/process.c, dlls/ntdll/sync.c, include/winnt.h, server/process.c, server/process.h, +# | server/protocol.def # | -if test "$enable_server_OpenProcess" -eq 1; then - patch_apply server-OpenProcess/0001-server-Return-error-when-opening-a-terminating-proce.patch +if test "$enable_server_JobObjects" -eq 1; then + patch_apply server-JobObjects/0001-kernel32-tests-Allow-multiple-subprocess-commands-in.patch + patch_apply server-JobObjects/0002-kernel32-tests-Add-tests-for-IsProcessInJob.patch + patch_apply server-JobObjects/0003-kernel32-tests-Add-tests-for-TerminateJobObject.patch + patch_apply server-JobObjects/0004-kernel32-tests-Add-tests-for-QueryInformationJobObje.patch + patch_apply server-JobObjects/0005-kernel32-tests-Add-tests-for-job-object-completion-p.patch + patch_apply server-JobObjects/0006-kernel32-tests-Add-tests-for-JOB_OBJECT_LIMIT_KILL_O.patch + patch_apply server-JobObjects/0007-kernel32-tests-Add-tests-for-JOB_OBJECT_LIMIT_ACTIVE.patch + patch_apply server-JobObjects/0008-kernel32-tests-Add-tests-for-JOB_OBJECT_LIMIT_BREAKA.patch + patch_apply server-JobObjects/0009-kernel32-tests-Add-tests-for-job-inheritance.patch + patch_apply server-JobObjects/0010-server-Basic-implementation-of-job-objects.patch ( - echo '+ { "Michael Müller", "server: Return error when opening a terminating process.", 3 },'; + echo '+ { "Sebastian Lackner", "kernel32/tests: Allow multiple subprocess commands in process tests.", 1 },'; + echo '+ { "Andrew Cook", "kernel32/tests: Add tests for IsProcessInJob.", 1 },'; + echo '+ { "Andrew Cook", "kernel32/tests: Add tests for TerminateJobObject.", 1 },'; + echo '+ { "Andrew Cook", "kernel32/tests: Add tests for QueryInformationJobObject.", 1 },'; + echo '+ { "Andrew Cook", "kernel32/tests: Add tests for job object completion ports.", 1 },'; + echo '+ { "Andrew Cook", "kernel32/tests: Add tests for JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE.", 1 },'; + echo '+ { "Andrew Cook", "kernel32/tests: Add tests for JOB_OBJECT_LIMIT_ACTIVE_PROCESS.", 1 },'; + echo '+ { "Andrew Cook", "kernel32/tests: Add tests for JOB_OBJECT_LIMIT_BREAKAWAY_OK.", 1 },'; + echo '+ { "Andrew Cook", "kernel32/tests: Add tests for job inheritance.", 1 },'; + echo '+ { "Andrew Cook", "server: Basic implementation of job objects.", 1 },'; + ) >> "$patchlist" +fi + +# Patchset server-Key_State +# | +# | This patchset fixes the following Wine bugs: +# | * [#27238] Fallback to global key state for threads without a queue +# | +# | Modified files: +# | * server/queue.c +# | +if test "$enable_server_Key_State" -eq 1; then + patch_apply server-Key_State/0001-server-Fall-back-to-global-key-state-when-thread-doe.patch + ( + echo '+ { "Sebastian Lackner", "server: Fall back to global key state when thread doesn'\''t have a queue.", 1 },'; ) >> "$patchlist" fi diff --git a/patches/kernel32-JobObjects/0001-kernel32-tests-Allow-multiple-subprocess-commands-in.patch b/patches/server-JobObjects/0001-kernel32-tests-Allow-multiple-subprocess-commands-in.patch similarity index 100% rename from patches/kernel32-JobObjects/0001-kernel32-tests-Allow-multiple-subprocess-commands-in.patch rename to patches/server-JobObjects/0001-kernel32-tests-Allow-multiple-subprocess-commands-in.patch diff --git a/patches/kernel32-JobObjects/0002-kernel32-tests-Add-tests-for-IsProcessInJob.patch b/patches/server-JobObjects/0002-kernel32-tests-Add-tests-for-IsProcessInJob.patch similarity index 100% rename from patches/kernel32-JobObjects/0002-kernel32-tests-Add-tests-for-IsProcessInJob.patch rename to patches/server-JobObjects/0002-kernel32-tests-Add-tests-for-IsProcessInJob.patch diff --git a/patches/kernel32-JobObjects/0003-kernel32-tests-Add-tests-for-TerminateJobObject.patch b/patches/server-JobObjects/0003-kernel32-tests-Add-tests-for-TerminateJobObject.patch similarity index 100% rename from patches/kernel32-JobObjects/0003-kernel32-tests-Add-tests-for-TerminateJobObject.patch rename to patches/server-JobObjects/0003-kernel32-tests-Add-tests-for-TerminateJobObject.patch diff --git a/patches/kernel32-JobObjects/0004-kernel32-tests-Add-tests-for-QueryInformationJobObje.patch b/patches/server-JobObjects/0004-kernel32-tests-Add-tests-for-QueryInformationJobObje.patch similarity index 100% rename from patches/kernel32-JobObjects/0004-kernel32-tests-Add-tests-for-QueryInformationJobObje.patch rename to patches/server-JobObjects/0004-kernel32-tests-Add-tests-for-QueryInformationJobObje.patch diff --git a/patches/kernel32-JobObjects/0005-kernel32-tests-Add-tests-for-job-object-completion-p.patch b/patches/server-JobObjects/0005-kernel32-tests-Add-tests-for-job-object-completion-p.patch similarity index 100% rename from patches/kernel32-JobObjects/0005-kernel32-tests-Add-tests-for-job-object-completion-p.patch rename to patches/server-JobObjects/0005-kernel32-tests-Add-tests-for-job-object-completion-p.patch diff --git a/patches/kernel32-JobObjects/0006-kernel32-tests-Add-tests-for-JOB_OBJECT_LIMIT_KILL_O.patch b/patches/server-JobObjects/0006-kernel32-tests-Add-tests-for-JOB_OBJECT_LIMIT_KILL_O.patch similarity index 100% rename from patches/kernel32-JobObjects/0006-kernel32-tests-Add-tests-for-JOB_OBJECT_LIMIT_KILL_O.patch rename to patches/server-JobObjects/0006-kernel32-tests-Add-tests-for-JOB_OBJECT_LIMIT_KILL_O.patch diff --git a/patches/kernel32-JobObjects/0007-kernel32-tests-Add-tests-for-JOB_OBJECT_LIMIT_ACTIVE.patch b/patches/server-JobObjects/0007-kernel32-tests-Add-tests-for-JOB_OBJECT_LIMIT_ACTIVE.patch similarity index 100% rename from patches/kernel32-JobObjects/0007-kernel32-tests-Add-tests-for-JOB_OBJECT_LIMIT_ACTIVE.patch rename to patches/server-JobObjects/0007-kernel32-tests-Add-tests-for-JOB_OBJECT_LIMIT_ACTIVE.patch diff --git a/patches/kernel32-JobObjects/0008-kernel32-tests-Add-tests-for-JOB_OBJECT_LIMIT_BREAKA.patch b/patches/server-JobObjects/0008-kernel32-tests-Add-tests-for-JOB_OBJECT_LIMIT_BREAKA.patch similarity index 100% rename from patches/kernel32-JobObjects/0008-kernel32-tests-Add-tests-for-JOB_OBJECT_LIMIT_BREAKA.patch rename to patches/server-JobObjects/0008-kernel32-tests-Add-tests-for-JOB_OBJECT_LIMIT_BREAKA.patch diff --git a/patches/kernel32-JobObjects/0009-kernel32-tests-Add-tests-for-job-inheritance.patch b/patches/server-JobObjects/0009-kernel32-tests-Add-tests-for-job-inheritance.patch similarity index 100% rename from patches/kernel32-JobObjects/0009-kernel32-tests-Add-tests-for-job-inheritance.patch rename to patches/server-JobObjects/0009-kernel32-tests-Add-tests-for-job-inheritance.patch diff --git a/patches/server-JobObjects/0010-server-Basic-implementation-of-job-objects.patch b/patches/server-JobObjects/0010-server-Basic-implementation-of-job-objects.patch new file mode 100644 index 00000000..4b22ca11 --- /dev/null +++ b/patches/server-JobObjects/0010-server-Basic-implementation-of-job-objects.patch @@ -0,0 +1,664 @@ +From 42af9f44c92dbe151b3ec5b4f57e941133349648 Mon Sep 17 00:00:00 2001 +From: Andrew Cook +Date: Thu, 26 Feb 2015 12:25:23 +1100 +Subject: server: Basic implementation of job objects. + +This patch includes a (hopefully) complete implementation of process +tracking in job objects, but no limits or events outside of those. + +This does not implement nested jobs as found in windows 8. + +Changes by Sebastian Lackner : +* Only use a single list instead of two for active / all processes. +* Various cleanups to fit with the rest of the wineserver coding style. +--- + dlls/kernel32/tests/process.c | 8 +- + dlls/ntdll/sync.c | 139 ++++++++++++++++++++--- + include/winnt.h | 4 + + server/process.c | 256 ++++++++++++++++++++++++++++++++++++++++++ + server/process.h | 3 + + server/protocol.def | 27 +++++ + 6 files changed, 417 insertions(+), 20 deletions(-) + +diff --git a/dlls/kernel32/tests/process.c b/dlls/kernel32/tests/process.c +index 9d7d8b8..3f78d74 100644 +--- a/dlls/kernel32/tests/process.c ++++ b/dlls/kernel32/tests/process.c +@@ -2244,7 +2244,6 @@ static void test_IsProcessInJob(void) + out = FALSE; + ret = pIsProcessInJob(pi.hProcess, job, &out); + ok(ret, "IsProcessInJob error %u\n", GetLastError()); +- todo_wine + ok(out, "IsProcessInJob returned out=%u\n", out); + + TerminateProcess(pi.hProcess, 0); +@@ -2255,7 +2254,6 @@ static void test_IsProcessInJob(void) + out = FALSE; + ret = pIsProcessInJob(pi.hProcess, job, &out); + ok(ret, "IsProcessInJob error %u\n", GetLastError()); +- todo_wine + ok(out, "IsProcessInJob returned out=%u\n", out); + + CloseHandle(pi.hProcess); +@@ -2282,13 +2280,11 @@ static void test_TerminateJobObject(void) + ok(ret, "TerminateJobObject error %u\n", GetLastError()); + + dwret = WaitForSingleObject(pi.hProcess, 500); +- todo_wine + ok(dwret == WAIT_OBJECT_0, "WaitForSingleObject returned %u\n", dwret); + if (dwret == WAIT_TIMEOUT) TerminateProcess(pi.hProcess, 0); + + ret = GetExitCodeProcess(pi.hProcess, &dwret); + ok(ret, "GetExitCodeProcess error %u\n", GetLastError()); +- todo_wine + ok(dwret == 123 || broken(dwret == 0) /* randomly fails on Win 2000 / XP */, + "wrong exitcode %u\n", dwret); + +@@ -2397,6 +2393,7 @@ static void test_CompletionPort(void) + port_info.CompletionKey = job; + port_info.CompletionPort = port; + ret = pSetInformationJobObject(job, JobObjectAssociateCompletionPortInformation, &port_info, sizeof(port_info)); ++ todo_wine + ok(ret, "SetInformationJobObject error %u\n", GetLastError()); + + create_process("wait", &pi); +@@ -2564,7 +2561,6 @@ static HANDLE test_LimitActiveProcesses(void) + out = FALSE; + ret = pIsProcessInJob(pi[0].hProcess, job, &out); + ok(ret, "IsProcessInJob error %u\n", GetLastError()); +- todo_wine + ok(out, "IsProcessInJob returned out=%u\n", out); + + dwret = WaitForSingleObject(pi[0].hProcess, 500); +@@ -2595,9 +2591,7 @@ static void test_BreakawayOk(HANDLE job) + snprintf(buffer, MAX_PATH, "\"%s\" tests/process.c %s", selfname, "exit"); + + ret = CreateProcessA(NULL, buffer, NULL, NULL, FALSE, CREATE_BREAKAWAY_FROM_JOB, NULL, NULL, &si, &pi); +- todo_wine + ok(!ret, "CreateProcessA expected failure\n"); +- todo_wine + expect_eq_d(ERROR_ACCESS_DENIED, GetLastError()); + + if (ret) +diff --git a/dlls/ntdll/sync.c b/dlls/ntdll/sync.c +index 08fab44..56714ef 100644 +--- a/dlls/ntdll/sync.c ++++ b/dlls/ntdll/sync.c +@@ -58,6 +58,7 @@ + #include "wine/server.h" + #include "wine/debug.h" + #include "ntdll_misc.h" ++#include "winnt.h" + + WINE_DEFAULT_DEBUG_CHANNEL(ntdll); + +@@ -567,9 +568,36 @@ NTSTATUS WINAPI NtQueryMutant(IN HANDLE handle, + */ + NTSTATUS WINAPI NtCreateJobObject( PHANDLE handle, ACCESS_MASK access, const OBJECT_ATTRIBUTES *attr ) + { +- FIXME( "stub: %p %x %s\n", handle, access, attr ? debugstr_us(attr->ObjectName) : "" ); +- *handle = (HANDLE)0xdead; +- return STATUS_SUCCESS; ++ DWORD len = attr && attr->ObjectName ? attr->ObjectName->Length : 0; ++ NTSTATUS ret; ++ struct security_descriptor *sd = NULL; ++ struct object_attributes objattr; ++ ++ if (len >= MAX_PATH * sizeof(WCHAR)) return STATUS_NAME_TOO_LONG; ++ ++ objattr.rootdir = wine_server_obj_handle( attr ? attr->RootDirectory : 0 ); ++ objattr.sd_len = 0; ++ objattr.name_len = len; ++ if (attr) ++ { ++ ret = NTDLL_create_struct_sd( attr->SecurityDescriptor, &sd, &objattr.sd_len ); ++ if (ret != STATUS_SUCCESS) return ret; ++ } ++ ++ SERVER_START_REQ( create_job ) ++ { ++ req->access = access; ++ req->attributes = attr ? attr->Attributes : 0; ++ wine_server_add_data( req, &objattr, sizeof(objattr) ); ++ if (objattr.sd_len) wine_server_add_data( req, sd, objattr.sd_len ); ++ if (len) wine_server_add_data( req, attr->ObjectName->Buffer, len ); ++ ret = wine_server_call( req ); ++ *handle = wine_server_ptr_handle( reply->handle ); ++ } ++ SERVER_END_REQ; ++ ++ NTDLL_free_struct_sd( sd ); ++ return ret; + } + + /****************************************************************************** +@@ -588,8 +616,19 @@ NTSTATUS WINAPI NtOpenJobObject( PHANDLE handle, ACCESS_MASK access, const OBJEC + */ + NTSTATUS WINAPI NtTerminateJobObject( HANDLE handle, NTSTATUS status ) + { +- FIXME( "stub: %p %x\n", handle, status ); +- return STATUS_SUCCESS; ++ NTSTATUS ret; ++ ++ TRACE( "(%p, %d)\n", handle, status ); ++ ++ SERVER_START_REQ( terminate_job ) ++ { ++ req->handle = wine_server_obj_handle( handle ); ++ req->status = status; ++ ret = wine_server_call( req ); ++ } ++ SERVER_END_REQ; ++ ++ return ret; + } + + /****************************************************************************** +@@ -599,8 +638,17 @@ NTSTATUS WINAPI NtTerminateJobObject( HANDLE handle, NTSTATUS status ) + NTSTATUS WINAPI NtQueryInformationJobObject( HANDLE handle, JOBOBJECTINFOCLASS class, PVOID info, + ULONG len, PULONG ret_len ) + { +- FIXME( "stub: %p %u %p %u %p\n", handle, class, info, len, ret_len ); +- return STATUS_NOT_IMPLEMENTED; ++ TRACE( "%p %u %p %u %p\n", handle, class, info, len, ret_len ); ++ ++ if (class >= MaxJobObjectInfoClass) ++ return STATUS_INVALID_PARAMETER; ++ ++ switch (class) ++ { ++ default: ++ FIXME( "stub: %p %u %p %u %p\n", handle, class, info, len, ret_len ); ++ return STATUS_NOT_IMPLEMENTED; ++ } + } + + /****************************************************************************** +@@ -609,8 +657,51 @@ NTSTATUS WINAPI NtQueryInformationJobObject( HANDLE handle, JOBOBJECTINFOCLASS c + */ + NTSTATUS WINAPI NtSetInformationJobObject( HANDLE handle, JOBOBJECTINFOCLASS class, PVOID info, ULONG len ) + { +- FIXME( "stub: %p %u %p %u\n", handle, class, info, len ); +- return STATUS_SUCCESS; ++ JOBOBJECT_BASIC_LIMIT_INFORMATION *basic_limit; ++ NTSTATUS status = STATUS_SUCCESS; ++ ++ TRACE( "(%p, %u, %p, %u)\n", handle, class, info, len ); ++ ++ if (class >= MaxJobObjectInfoClass) ++ return STATUS_INVALID_PARAMETER; ++ ++ switch (class) ++ { ++ ++ case JobObjectExtendedLimitInformation: ++ if (len != sizeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION)) ++ return STATUS_INVALID_PARAMETER; ++ ++ basic_limit = &(((JOBOBJECT_EXTENDED_LIMIT_INFORMATION *)info)->BasicLimitInformation); ++ if (basic_limit->LimitFlags & ~JOB_OBJECT_EXTENDED_LIMIT_VALID_FLAGS) ++ return STATUS_INVALID_PARAMETER; ++ ++ goto set_basic_limits; ++ ++ case JobObjectBasicLimitInformation: ++ if (len != sizeof(JOBOBJECT_BASIC_LIMIT_INFORMATION)) ++ return STATUS_INVALID_PARAMETER; ++ ++ basic_limit = info; ++ if (basic_limit->LimitFlags & ~JOB_OBJECT_BASIC_LIMIT_VALID_FLAGS) ++ return STATUS_INVALID_PARAMETER; ++ ++ set_basic_limits: ++ SERVER_START_REQ( job_set_limits ) ++ { ++ req->handle = wine_server_obj_handle( handle ); ++ req->limit_flags = basic_limit->LimitFlags; ++ status = wine_server_call( req ); ++ } ++ SERVER_END_REQ; ++ break; ++ ++ default: ++ FIXME( "stub: %p %u %p %u\n", handle, class, info, len ); ++ return STATUS_NOT_IMPLEMENTED; ++ } ++ ++ return status; + } + + /****************************************************************************** +@@ -619,8 +710,19 @@ NTSTATUS WINAPI NtSetInformationJobObject( HANDLE handle, JOBOBJECTINFOCLASS cla + */ + NTSTATUS WINAPI NtIsProcessInJob( HANDLE process, HANDLE job ) + { +- FIXME( "stub: %p %p\n", process, job ); +- return STATUS_PROCESS_NOT_IN_JOB; ++ NTSTATUS status; ++ ++ TRACE( "(%p %p)\n", job, process ); ++ ++ SERVER_START_REQ( process_in_job ) ++ { ++ req->process_handle = wine_server_obj_handle( process ); ++ req->job_handle = wine_server_obj_handle( job ); ++ status = wine_server_call( req ); ++ } ++ SERVER_END_REQ; ++ ++ return status; + } + + /****************************************************************************** +@@ -629,8 +731,19 @@ NTSTATUS WINAPI NtIsProcessInJob( HANDLE process, HANDLE job ) + */ + NTSTATUS WINAPI NtAssignProcessToJobObject( HANDLE job, HANDLE process ) + { +- FIXME( "stub: %p %p\n", job, process ); +- return STATUS_SUCCESS; ++ NTSTATUS status; ++ ++ TRACE( "(%p %p)\n", job, process ); ++ ++ SERVER_START_REQ( job_assign ) ++ { ++ req->job_handle = wine_server_obj_handle( job ); ++ req->process_handle = wine_server_obj_handle( process ); ++ status = wine_server_call( req ); ++ } ++ SERVER_END_REQ; ++ ++ return status; + } + + /* +diff --git a/include/winnt.h b/include/winnt.h +index 4b06b2c..c2aa50e 100644 +--- a/include/winnt.h ++++ b/include/winnt.h +@@ -5610,6 +5610,10 @@ typedef struct _JOBOBJECT_EXTENDED_LIMIT_INFORMATION { + #define JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE 0x00002000 + #define JOB_OBJECT_LIMIT_SUBSET_AFFINITY 0x00004000 + ++#define JOB_OBJECT_LIMIT_VALID_FLAGS 0x0007ffff ++#define JOB_OBJECT_BASIC_LIMIT_VALID_FLAGS 0x000000ff ++#define JOB_OBJECT_EXTENDED_LIMIT_VALID_FLAGS 0x00007fff ++ + typedef enum _LOGICAL_PROCESSOR_RELATIONSHIP + { + RelationProcessorCore = 0, +diff --git a/server/process.c b/server/process.c +index 0712a5b..850a9c8 100644 +--- a/server/process.c ++++ b/server/process.c +@@ -65,6 +65,7 @@ static unsigned int process_map_access( struct object *obj, unsigned int access + static struct security_descriptor *process_get_sd( struct object *obj ); + static void process_poll_event( struct fd *fd, int event ); + static void process_destroy( struct object *obj ); ++static void terminate_process( struct process *process, struct thread *skip, int exit_code ); + + static const struct object_ops process_ops = + { +@@ -134,6 +135,152 @@ static const struct object_ops startup_info_ops = + startup_info_destroy /* destroy */ + }; + ++/* job object */ ++ ++static void job_dump( struct object *obj, int verbose ); ++static struct object_type *job_get_type( struct object *obj ); ++static int job_signaled( struct object *obj, struct wait_queue_entry *entry ); ++static unsigned int job_map_access( struct object *obj, unsigned int access ); ++static void job_destroy( struct object *obj ); ++ ++struct job ++{ ++ struct object obj; /* object header */ ++ struct list process_list; /* list of all processes */ ++ int num_processes; /* count of running processes */ ++ int limit_flags; /* limit flags */ ++}; ++ ++static const struct object_ops job_ops = ++{ ++ sizeof(struct job), /* size */ ++ job_dump, /* dump */ ++ job_get_type, /* get_type */ ++ add_queue, /* add_queue */ ++ remove_queue, /* remove_queue */ ++ job_signaled, /* signaled */ ++ no_satisfied, /* satisfied */ ++ no_signal, /* signal */ ++ no_get_fd, /* get_fd */ ++ job_map_access, /* map_access */ ++ default_get_sd, /* get_sd */ ++ default_set_sd, /* set_sd */ ++ no_lookup_name, /* lookup_name */ ++ no_open_file, /* open_file */ ++ no_close_handle, /* close_handle */ ++ job_destroy /* destroy */ ++}; ++ ++static struct job *create_job_object( struct directory *root, const struct unicode_str *name, ++ unsigned int attr, const struct security_descriptor *sd ) ++{ ++ struct job *job; ++ ++ if ((job = create_named_object_dir( root, name, attr, &job_ops ))) ++ { ++ if (get_error() != STATUS_OBJECT_NAME_EXISTS) ++ { ++ /* initialize it if it didn't already exist */ ++ if (sd) default_set_sd( &job->obj, sd, OWNER_SECURITY_INFORMATION | ++ GROUP_SECURITY_INFORMATION | ++ DACL_SECURITY_INFORMATION | ++ SACL_SECURITY_INFORMATION ); ++ list_init( &job->process_list ); ++ job->num_processes = 0; ++ job->limit_flags = 0; ++ } ++ } ++ return job; ++} ++ ++static struct job *get_job_obj( struct process *process, obj_handle_t handle, unsigned int access ) ++{ ++ return (struct job *)get_handle_obj( process, handle, access, &job_ops ); ++} ++ ++static struct object_type *job_get_type( struct object *obj ) ++{ ++ static const WCHAR name[] = {'J','o','b'}; ++ static const struct unicode_str str = { name, sizeof(name) }; ++ return get_object_type( &str ); ++}; ++ ++static unsigned int job_map_access( struct object *obj, unsigned int access ) ++{ ++ if (access & GENERIC_READ) access |= STANDARD_RIGHTS_READ; ++ if (access & GENERIC_WRITE) access |= STANDARD_RIGHTS_WRITE; ++ if (access & GENERIC_EXECUTE) access |= STANDARD_RIGHTS_EXECUTE; ++ if (access & GENERIC_ALL) access |= JOB_OBJECT_ALL_ACCESS; ++ return access & ~(GENERIC_READ | GENERIC_WRITE | GENERIC_EXECUTE | GENERIC_ALL); ++} ++ ++static int add_job_process( struct job *job, struct process *process ) ++{ ++ assert( job->obj.ops == &job_ops ); ++ ++ if (process->job) ++ { ++ set_error(STATUS_ACCESS_DENIED); ++ return 0; ++ } ++ ++ process->job = (struct job *)grab_object( job ); ++ list_add_tail( &job->process_list, &process->job_entry ); ++ job->num_processes++; ++ ++ return 1; ++} ++ ++/* called when a process has terminated, allow one additional process */ ++static void release_job_process( struct process *process ) ++{ ++ struct job *job = process->job; ++ ++ if (!job) ++ return; ++ ++ assert( job->obj.ops == &job_ops ); ++ assert( job->num_processes ); ++ job->num_processes--; ++} ++ ++static void terminate_job( struct job *job, int exit_code ) ++{ ++ for (;;) /* restart from the beginning of the list every time */ ++ { ++ struct process *process; ++ ++ /* find the first process associcated 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 ); ++ } ++} ++ ++static void job_destroy( struct object *obj ) ++{ ++ struct job *job = (struct job *)obj; ++ assert( obj->ops == &job_ops ); ++ ++ assert( !job->num_processes ); ++ assert( list_empty(&job->process_list) ); ++} ++ ++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) ); ++} ++ ++static int job_signaled( struct object *obj, struct wait_queue_entry *entry ) ++{ ++ return 0; ++} + + struct ptid_entry + { +@@ -327,6 +474,7 @@ struct thread *create_process( int fd, struct thread *parent_thread, int inherit + process->debug_children = 0; + process->is_terminating = 0; + process->is_terminated = 0; ++ process->job = NULL; + process->console = NULL; + process->startup_state = STARTUP_IN_PROGRESS; + process->startup_info = NULL; +@@ -425,6 +573,12 @@ static void process_destroy( struct object *obj ) + + close_process_handles( process ); + set_process_startup_state( process, STARTUP_ABORTED ); ++ ++ if (process->job) ++ { ++ list_remove( &process->job_entry ); ++ release_object( process->job ); ++ } + if (process->console) release_object( process->console ); + if (process->parent) release_object( process->parent ); + if (process->msg_fd) release_object( process->msg_fd ); +@@ -709,6 +863,7 @@ static void process_killed( struct process *process ) + remove_process_locks( process ); + set_process_startup_state( process, STARTUP_ABORTED ); + finish_process_tracing( process ); ++ release_job_process( process ); + start_sigkill_timer( process ); + wake_up( &process->obj, 0 ); + } +@@ -954,6 +1109,14 @@ DECL_HANDLER(new_process) + return; + } + ++ if (parent->job && (req->create_flags & CREATE_BREAKAWAY_FROM_JOB) && ++ !(parent->job->limit_flags & (JOB_OBJECT_LIMIT_BREAKAWAY_OK | JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK))) ++ { ++ set_error( STATUS_ACCESS_DENIED ); ++ close( socket_fd ); ++ return; ++ } ++ + if (!req->info_size) /* create an orphaned process */ + { + create_process( socket_fd, NULL, 0 ); +@@ -1024,6 +1187,12 @@ DECL_HANDLER(new_process) + && !(req->create_flags & DEBUG_ONLY_THIS_PROCESS); + process->startup_info = (struct startup_info *)grab_object( info ); + ++ if (parent->job && !((req->create_flags & CREATE_BREAKAWAY_FROM_JOB) || ++ (parent->job->limit_flags & JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK))) ++ { ++ add_job_process( parent->job, process ); ++ } ++ + /* connect to the window station */ + connect_process_winstation( process, current ); + +@@ -1361,3 +1530,90 @@ DECL_HANDLER(make_process_system) + shutdown_timeout = add_timeout_user( master_socket_timeout, server_shutdown_timeout, NULL ); + } + } ++ ++/* create a new job object */ ++DECL_HANDLER(create_job) ++{ ++ struct job *job; ++ struct unicode_str name; ++ struct directory *root = NULL; ++ const struct object_attributes *objattr = get_req_data(); ++ const struct security_descriptor *sd; ++ ++ if (!objattr_is_valid( objattr, get_req_data_size() )) return; ++ ++ sd = objattr->sd_len ? (const struct security_descriptor *)(objattr + 1) : NULL; ++ objattr_get_name( objattr, &name ); ++ ++ if (objattr->rootdir && !(root = get_directory_obj( current->process, objattr->rootdir, 0 ))) return; ++ ++ if ((job = create_job_object( root, &name, req->attributes, sd ))) ++ { ++ if (get_error() == STATUS_OBJECT_NAME_EXISTS) ++ reply->handle = alloc_handle( current->process, job, req->access, req->attributes ); ++ else ++ reply->handle = alloc_handle_no_access_check( current->process, job, req->access, req->attributes ); ++ release_object( job ); ++ } ++ if (root) release_object( root ); ++} ++ ++/* assign a job object to a process */ ++DECL_HANDLER(job_assign) ++{ ++ struct process *process; ++ struct job *job; ++ ++ if (!(job = get_job_obj( current->process, req->job_handle, JOB_OBJECT_ASSIGN_PROCESS ))) ++ return; ++ ++ if ((process = get_process_from_handle( req->process_handle, PROCESS_SET_QUOTA | PROCESS_TERMINATE ))) ++ { ++ add_job_process( job, process ); ++ release_object(process); ++ } ++ ++ release_object(job); ++} ++ ++/* check if a process is associated with a job */ ++DECL_HANDLER(process_in_job) ++{ ++ struct process *process; ++ struct job *job; ++ ++ if (!(job = get_job_obj( current->process, req->job_handle, JOB_OBJECT_ASSIGN_PROCESS ))) ++ return; ++ ++ if ((process = get_process_from_handle( req->process_handle, PROCESS_QUERY_INFORMATION ))) ++ { ++ set_error((process->job == job) ? STATUS_PROCESS_IN_JOB : STATUS_PROCESS_NOT_IN_JOB); ++ release_object(process); ++ } ++ ++ release_object(job); ++} ++ ++/* terminate all processes associated with the job */ ++DECL_HANDLER(terminate_job) ++{ ++ struct job *job; ++ ++ if ((job = get_job_obj( current->process, req->handle, JOB_OBJECT_TERMINATE ))) ++ { ++ terminate_job(job, req->status); ++ release_object(job); ++ } ++} ++ ++/* update limits of the job object */ ++DECL_HANDLER(job_set_limits) ++{ ++ struct job *job; ++ ++ if ((job = get_job_obj( current->process, req->handle, JOB_OBJECT_SET_ATTRIBUTES ))) ++ { ++ job->limit_flags = req->limit_flags; ++ release_object(job); ++ } ++} +diff --git a/server/process.h b/server/process.h +index da6d3da..ae83b0e 100644 +--- a/server/process.h ++++ b/server/process.h +@@ -26,6 +26,7 @@ + struct atom_table; + struct handle_table; + struct startup_info; ++struct job; + + /* process startup state */ + enum startup_state { STARTUP_IN_PROGRESS, STARTUP_DONE, STARTUP_ABORTED }; +@@ -76,6 +77,8 @@ struct process + unsigned int debug_children:1;/* also debug all child processes */ + unsigned int is_terminating:1;/* is process terminating? */ + unsigned int is_terminated:1; /* is process terminated? */ ++ struct job *job; /* job object ascoicated with this process */ ++ struct list job_entry; /* list entry for job object */ + struct list locks; /* list of file locks owned by the process */ + struct list classes; /* window classes owned by the process */ + struct console_input*console; /* console input */ +diff --git a/server/protocol.def b/server/protocol.def +index fc6bec5..cc39ef1 100644 +--- a/server/protocol.def ++++ b/server/protocol.def +@@ -681,6 +681,33 @@ struct rawinput_device + obj_handle_t thandle; /* thread handle (in the current process) */ + @END + ++@REQ(create_job) ++ unsigned int access; /* wanted access rights */ ++ unsigned int attributes; /* object attributes */ ++ VARARG(objattr,object_attributes); /* object attributes */ ++@REPLY ++ obj_handle_t handle; /* handle to the job */ ++@END ++ ++@REQ(terminate_job) ++ obj_handle_t handle; ++ int status; ++@END ++ ++@REQ(process_in_job) ++ obj_handle_t job_handle; ++ obj_handle_t process_handle; ++@END ++ ++@REQ(job_assign) ++ obj_handle_t job_handle; ++ obj_handle_t process_handle; ++@END ++ ++@REQ(job_set_limits) ++ obj_handle_t handle; ++ unsigned int limit_flags; ++@END + + /* Retrieve information about a newly started process */ + @REQ(get_new_process_info) +-- +2.3.0 + diff --git a/patches/server-JobObjects/definition b/patches/server-JobObjects/definition new file mode 100644 index 00000000..9f06def6 --- /dev/null +++ b/patches/server-JobObjects/definition @@ -0,0 +1,3 @@ +Depends: kernel32-Console_Handles +Depends: server-OpenProcess +Depends: server-Misc_ACL