From 91a03090c8aafda8f47004734a04a0ac8d83a901 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. (rev 2) 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. Changes in revision 2: * Fix a wineserver crash when terminated processes are added to a job. --- dlls/kernel32/tests/process.c | 10 +- dlls/ntdll/sync.c | 139 ++++++++++++++++++++--- include/winnt.h | 4 + server/process.c | 259 ++++++++++++++++++++++++++++++++++++++++++ server/process.h | 3 + server/protocol.def | 27 +++++ 6 files changed, 420 insertions(+), 22 deletions(-) diff --git a/dlls/kernel32/tests/process.c b/dlls/kernel32/tests/process.c index a464db2..e23d046 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); @@ -2269,9 +2267,7 @@ static void test_IsProcessInJob(void) SetLastError(0xdeadbeef); ret = pAssignProcessToJobObject(job, pi.hProcess); - todo_wine ok(!ret, "AssignProcessToJobObject unexpectedly succeeded\n"); - todo_wine expect_eq_d(ERROR_ACCESS_DENIED, GetLastError()); CloseHandle(pi.hProcess); @@ -2299,13 +2295,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); @@ -2414,6 +2408,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); @@ -2581,7 +2576,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); @@ -2612,9 +2606,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..6384203 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,157 @@ 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->running_threads) + { + set_error( STATUS_PROCESS_IS_TERMINATING ); + return 0; + } + 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 +479,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 +578,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 +868,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 +1114,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 +1192,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 +1535,88 @@ 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 = get_job_obj( current->process, req->job_handle, JOB_OBJECT_ASSIGN_PROCESS ); + + if (job) + { + 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 = get_job_obj( current->process, req->job_handle, JOB_OBJECT_ASSIGN_PROCESS ); + + if (job) + { + 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 = get_job_obj( current->process, req->handle, JOB_OBJECT_TERMINATE ); + + if (job) + { + terminate_job( job, req->status ); + release_object( job ); + } +} + +/* update limits of the job object */ +DECL_HANDLER(job_set_limits) +{ + struct job *job = get_job_obj( current->process, req->handle, JOB_OBJECT_SET_ATTRIBUTES ); + + if (job) + { + 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