diff --git a/include/wine/svcctl.idl b/include/wine/svcctl.idl index 0c0b49b66ba..9fd5ca4c79a 100644 --- a/include/wine/svcctl.idl +++ b/include/wine/svcctl.idl @@ -36,6 +36,7 @@ cpp_quote("#define SVCCTL_STARTED_EVENT {'_','_','w','i','n','e','_','S','v','c' /* Service startup protocol over control pipe - not compatible with Windows */ cpp_quote("#define SERVICE_PROTOCOL_MAGIC 0x57494e45") cpp_quote("#define SERVICE_CONTROL_START 0") +cpp_quote("#define SERVICE_CONTROL_FORWARD_FLAG 0x80000000") typedef struct service_start_info_t { diff --git a/programs/services/rpc.c b/programs/services/rpc.c index 468b380cb74..08b5776aebd 100644 --- a/programs/services/rpc.c +++ b/programs/services/rpc.c @@ -85,6 +85,7 @@ struct sc_lock struct scmdatabase *db; }; +static const WCHAR emptyW[] = {0}; static PTP_CLEANUP_GROUP cleanup_group; HANDLE exit_event; @@ -125,6 +126,44 @@ static void terminate_after_timeout(struct process_entry *process, DWORD timeout release_process(process); } +static void CALLBACK shutdown_callback(TP_CALLBACK_INSTANCE *instance, void *context) +{ + struct process_entry *process = context; + DWORD result; + + result = WaitForSingleObject(process->control_mutex, 30000); + if (result == WAIT_OBJECT_0) + { + process_send_control(process, FALSE, emptyW, SERVICE_CONTROL_STOP, NULL, 0, &result); + ReleaseMutex(process->control_mutex); + } + + release_process(process); +} + +static void shutdown_shared_process(struct process_entry *process) +{ + TP_CALLBACK_ENVIRON environment; + struct service_entry *service; + struct scmdatabase *db = process->db; + + scmdatabase_lock(db); + LIST_FOR_EACH_ENTRY(service, &db->services, struct service_entry, entry) + { + if (service->process != process) continue; + service->status.dwCurrentState = SERVICE_STOP_PENDING; + } + scmdatabase_unlock(db); + + memset(&environment, 0, sizeof(environment)); + environment.Version = 1; + environment.CleanupGroup = cleanup_group; + environment.CleanupGroupCancelCallback = group_cancel_callback; + + if (!TrySubmitThreadpoolCallback(shutdown_callback, grab_process(process), &environment)) + release_process(process); +} + static void free_service_strings(struct service_entry *old, struct service_entry *new) { QUERY_SERVICE_CONFIGW *old_cfg = &old->config; @@ -763,6 +802,8 @@ DWORD __cdecl svcctl_SetServiceStatus( service->service_entry->process = NULL; if (!--process->use_count) terminate_after_timeout(process, service_kill_timeout); + if (service->service_entry->shared_process && process->use_count <= 1) + shutdown_shared_process(process); release_process(process); } @@ -1030,13 +1071,21 @@ static BOOL process_send_command(struct process_entry *process, const void *data /****************************************************************************** * process_send_control */ -BOOL process_send_control(struct process_entry *process, const WCHAR *name, DWORD control, - const BYTE *data, DWORD data_size, DWORD *result) +BOOL process_send_control(struct process_entry *process, BOOL shared_process, const WCHAR *name, + DWORD control, const BYTE *data, DWORD data_size, DWORD *result) { service_start_info *ssi; DWORD len; BOOL r; + if (shared_process) + { + control |= SERVICE_CONTROL_FORWARD_FLAG; + data = (BYTE *)name; + data_size = (strlenW(name) + 1) * sizeof(WCHAR); + name = emptyW; + } + /* calculate how much space we need to send the startup info */ len = (strlenW(name) + 1) * sizeof(WCHAR) + data_size; @@ -1086,6 +1135,7 @@ DWORD __cdecl svcctl_ControlService( DWORD access_required; struct sc_service_handle *service; struct process_entry *process; + BOOL shared_process; DWORD result; WINE_TRACE("(%p, %d, %p)\n", hService, dwControl, lpServiceStatus); @@ -1165,6 +1215,7 @@ DWORD __cdecl svcctl_ControlService( /* Hold a reference to the process while sending the command. */ process = grab_process(service->service_entry->process); + shared_process = service->service_entry->shared_process; service_unlock(service->service_entry); if (!process) @@ -1177,7 +1228,8 @@ DWORD __cdecl svcctl_ControlService( return ERROR_SERVICE_REQUEST_TIMEOUT; } - if (process_send_control(process, service->service_entry->name, dwControl, NULL, 0, &result)) + if (process_send_control(process, shared_process, service->service_entry->name, + dwControl, NULL, 0, &result)) result = ERROR_SUCCESS; if (lpServiceStatus) diff --git a/programs/services/services.c b/programs/services/services.c index 04f440fc64c..8df75cbe4aa 100644 --- a/programs/services/services.c +++ b/programs/services/services.c @@ -683,35 +683,6 @@ static DWORD get_service_binary_path(const struct service_entry *service_entry, ExpandEnvironmentStringsW(service_entry->config.lpBinaryPathName, *path, size); - if (service_entry->config.dwServiceType == SERVICE_KERNEL_DRIVER || - service_entry->config.dwServiceType == SERVICE_FILE_SYSTEM_DRIVER) - { - static const WCHAR winedeviceW[] = {'\\','w','i','n','e','d','e','v','i','c','e','.','e','x','e',' ',0}; - WCHAR system_dir[MAX_PATH]; - DWORD type, len; - - GetSystemDirectoryW( system_dir, MAX_PATH ); - if (is_win64) - { - if (!GetBinaryTypeW( *path, &type )) - { - HeapFree( GetProcessHeap(), 0, *path ); - return GetLastError(); - } - if (type == SCS_32BIT_BINARY) GetSystemWow64DirectoryW( system_dir, MAX_PATH ); - } - - len = strlenW( system_dir ) + sizeof(winedeviceW)/sizeof(WCHAR) + strlenW(service_entry->name); - HeapFree( GetProcessHeap(), 0, *path ); - if (!(*path = HeapAlloc( GetProcessHeap(), 0, len * sizeof(WCHAR) ))) - return ERROR_NOT_ENOUGH_SERVER_MEMORY; - - lstrcpyW( *path, system_dir ); - lstrcatW( *path, winedeviceW ); - lstrcatW( *path, service_entry->name ); - return ERROR_SUCCESS; - } - /* if service image is configured to systemdir, redirect it to wow64 systemdir */ if (service_entry->is_wow64) { @@ -743,13 +714,83 @@ static DWORD get_service_binary_path(const struct service_entry *service_entry, return ERROR_SUCCESS; } -static DWORD service_start_process(struct service_entry *service_entry, struct process_entry **new_process) +static DWORD get_winedevice_binary_path(WCHAR **path, BOOL *is_wow64) +{ + static const WCHAR winedeviceW[] = {'\\','w','i','n','e','d','e','v','i','c','e','.','e','x','e',0}; + WCHAR system_dir[MAX_PATH]; + DWORD type; + + if (!is_win64) + *is_wow64 = FALSE; + else if (GetBinaryTypeW(*path, &type)) + *is_wow64 = (type == SCS_32BIT_BINARY); + else + return GetLastError(); + + GetSystemDirectoryW(system_dir, MAX_PATH); + HeapFree(GetProcessHeap(), 0, *path); + if (!(*path = HeapAlloc(GetProcessHeap(), 0, strlenW(system_dir) * sizeof(WCHAR) + sizeof(winedeviceW)))) + return ERROR_NOT_ENOUGH_SERVER_MEMORY; + + strcpyW(*path, system_dir); + strcatW(*path, winedeviceW); + return ERROR_SUCCESS; +} + +static DWORD add_winedevice_service(const struct service_entry *service, WCHAR *path, BOOL is_wow64, + struct service_entry **entry) +{ + static const WCHAR format[] = {'W','i','n','e','d','e','v','i','c','e','%','u',0}; + static WCHAR name[sizeof(format)/sizeof(WCHAR) + 10]; /* strlenW("4294967295") */ + static DWORD current = 0; + struct scmdatabase *db = service->db; + DWORD err; + + for (;;) + { + sprintfW(name, format, ++current); + if (!scmdatabase_find_service(db, name)) break; + } + + err = service_create(name, entry); + if (err != ERROR_SUCCESS) + return err; + + (*entry)->is_wow64 = is_wow64; + (*entry)->config.dwServiceType = SERVICE_WIN32_OWN_PROCESS; + (*entry)->config.dwStartType = SERVICE_DEMAND_START; + (*entry)->status.dwServiceType = (*entry)->config.dwServiceType; + + if (!((*entry)->config.lpBinaryPathName = strdupW(path))) + goto error; + if (!((*entry)->config.lpServiceStartName = strdupW(SZ_LOCAL_SYSTEM))) + goto error; + if (!((*entry)->config.lpDisplayName = strdupW(name))) + goto error; + if (service->config.lpLoadOrderGroup && + !((*entry)->config.lpLoadOrderGroup = strdupW(service->config.lpLoadOrderGroup))) + goto error; + + (*entry)->db = db; + + list_add_tail(&db->services, &(*entry)->entry); + mark_for_delete(*entry); + return ERROR_SUCCESS; + +error: + free_service_entry(*entry); + return ERROR_NOT_ENOUGH_SERVER_MEMORY; +} + +static DWORD service_start_process(struct service_entry *service_entry, struct process_entry **new_process, + BOOL *shared_process) { struct process_entry *process; PROCESS_INFORMATION pi; STARTUPINFOW si; + BOOL is_wow64 = FALSE; HANDLE token; - LPWSTR path = NULL; + WCHAR *path; DWORD err; BOOL r; @@ -769,18 +810,80 @@ static DWORD service_start_process(struct service_entry *service_entry, struct p service_entry->force_shutdown = FALSE; + if ((err = get_service_binary_path(service_entry, &path))) + { + service_unlock(service_entry); + return err; + } + + if (service_entry->config.dwServiceType == SERVICE_KERNEL_DRIVER || + service_entry->config.dwServiceType == SERVICE_FILE_SYSTEM_DRIVER) + { + struct service_entry *winedevice_entry; + WCHAR *group; + + if ((err = get_winedevice_binary_path(&path, &is_wow64))) + { + service_unlock(service_entry); + HeapFree(GetProcessHeap(), 0, path); + return err; + } + + err = add_winedevice_service(service_entry, path, is_wow64, &winedevice_entry); + HeapFree(GetProcessHeap(), 0, path); + if (err != ERROR_SUCCESS) + { + service_unlock(service_entry); + return err; + } + + group = strdupW(winedevice_entry->config.lpLoadOrderGroup); + service_unlock(service_entry); + + err = service_start(winedevice_entry, group != NULL, (const WCHAR **)&group); + HeapFree(GetProcessHeap(), 0, group); + if (err != ERROR_SUCCESS) + { + release_service(winedevice_entry); + return err; + } + + service_lock(service_entry); + process = grab_process(winedevice_entry->process); + release_service(winedevice_entry); + + if (!process) + { + service_unlock(service_entry); + return ERROR_SERVICE_REQUEST_TIMEOUT; + } + + service_entry->status.dwCurrentState = SERVICE_START_PENDING; + service_entry->status.dwControlsAccepted = 0; + ResetEvent(service_entry->status_changed_event); + + service_entry->process = grab_process(process); + service_entry->shared_process = *shared_process = TRUE; + process->use_count++; + service_unlock(service_entry); + + err = WaitForSingleObject(process->control_mutex, 30000); + if (err != WAIT_OBJECT_0) + { + release_process(process); + return ERROR_SERVICE_REQUEST_TIMEOUT; + } + + *new_process = process; + return ERROR_SUCCESS; + } + if ((err = process_create(service_get_pipe_name(), &process))) { WINE_ERR("failed to create process object for %s, error = %u\n", wine_dbgstr_w(service_entry->name), err); service_unlock(service_entry); - return err; - } - - if ((err = get_service_binary_path(service_entry, &path))) - { - service_unlock(service_entry); - free_process_entry(process); + HeapFree(GetProcessHeap(), 0, path); return err; } @@ -804,12 +907,12 @@ static DWORD service_start_process(struct service_entry *service_entry, struct p scmdatabase_add_process(service_entry->db, process); service_entry->process = grab_process(process); + service_entry->shared_process = *shared_process = FALSE; process->use_count++; - service_unlock(service_entry); r = CreateProcessW(NULL, path, NULL, NULL, FALSE, CREATE_UNICODE_ENVIRONMENT, environment, NULL, &si, &pi); - HeapFree(GetProcessHeap(),0,path); + HeapFree(GetProcessHeap(), 0, path); if (!r) { err = GetLastError(); @@ -846,8 +949,8 @@ static DWORD service_wait_for_startup(struct service_entry *service, struct proc /****************************************************************************** * process_send_start_message */ -static DWORD process_send_start_message(struct process_entry *process, const WCHAR *name, - const WCHAR **argv, DWORD argc) +static DWORD process_send_start_message(struct process_entry *process, BOOL shared_process, + const WCHAR *name, const WCHAR **argv, DWORD argc) { OVERLAPPED overlapped; DWORD i, len, result; @@ -896,7 +999,8 @@ static DWORD process_send_start_message(struct process_entry *process, const WCH } *p = 0; - if (!process_send_control(process, name, SERVICE_CONTROL_START, (const BYTE *)str, len, &result)) + if (!process_send_control(process, shared_process, name, + SERVICE_CONTROL_START, (const BYTE *)str, len, &result)) result = ERROR_SERVICE_REQUEST_TIMEOUT; HeapFree(GetProcessHeap(), 0, str); @@ -906,21 +1010,28 @@ static DWORD process_send_start_message(struct process_entry *process, const WCH DWORD service_start(struct service_entry *service, DWORD service_argc, LPCWSTR *service_argv) { struct process_entry *process = NULL; + BOOL shared_process; DWORD err; - err = service_start_process(service, &process); + err = service_start_process(service, &process, &shared_process); if (err == ERROR_SUCCESS) { - err = process_send_start_message(process, service->name, service_argv, service_argc); + err = process_send_start_message(process, shared_process, service->name, service_argv, service_argc); if (err == ERROR_SUCCESS) err = service_wait_for_startup(service, process); - if (err == ERROR_SUCCESS) - ReleaseMutex(process->control_mutex); - else - process_terminate(process); + if (err != ERROR_SUCCESS) + { + service_lock(service); + service->status.dwCurrentState = SERVICE_STOPPED; + service->process = NULL; + if (!--process->use_count) process_terminate(process); + release_process(process); + service_unlock(service); + } + ReleaseMutex(process->control_mutex); release_process(process); } diff --git a/programs/services/services.h b/programs/services/services.h index 126ef2698e8..cda70e0b604 100644 --- a/programs/services/services.h +++ b/programs/services/services.h @@ -59,6 +59,7 @@ struct service_entry LPWSTR dependOnServices; LPWSTR dependOnGroups; struct process_entry *process; + BOOL shared_process; BOOL force_shutdown; BOOL marked_for_delete; BOOL is_wow64; @@ -95,8 +96,8 @@ DWORD service_start(struct service_entry *service, DWORD service_argc, LPCWSTR * struct process_entry *grab_process(struct process_entry *process); void release_process(struct process_entry *process); -BOOL process_send_control(struct process_entry *process, const WCHAR *name, DWORD control, - const BYTE *data, DWORD data_size, DWORD *result); +BOOL process_send_control(struct process_entry *process, BOOL winedevice, const WCHAR *name, + DWORD control, const BYTE *data, DWORD data_size, DWORD *result); void process_terminate(struct process_entry *process); extern DWORD service_pipe_timeout; diff --git a/programs/winedevice/device.c b/programs/winedevice/device.c index 79d56ecd4f9..b261a4a78b6 100644 --- a/programs/winedevice/device.c +++ b/programs/winedevice/device.c @@ -43,6 +43,7 @@ WINE_DECLARE_DEBUG_CHANNEL(relay); extern NTSTATUS CDECL wine_ntoskrnl_main_loop( HANDLE stop_event ); +static const WCHAR winedeviceW[] = {'w','i','n','e','d','e','v','i','c','e',0}; static SERVICE_STATUS_HANDLE service_handle; static PTP_CLEANUP_GROUP cleanup_group; static SC_HANDLE manager_handle; @@ -486,20 +487,57 @@ static void shutdown_drivers( void ) shutdown_in_progress = TRUE; } +static DWORD device_handler( DWORD ctrl, const WCHAR *driver_name ) +{ + struct wine_rb_entry *entry; + DWORD result = NO_ERROR; + + if (shutdown_in_progress) + return ERROR_SERVICE_CANNOT_ACCEPT_CTRL; + + EnterCriticalSection( &drivers_cs ); + entry = wine_rb_get( &wine_drivers, driver_name ); + + switch (ctrl) + { + case SERVICE_CONTROL_START: + if (entry) break; + result = RtlNtStatusToDosError(create_driver( driver_name )); + break; + + case SERVICE_CONTROL_STOP: + if (!entry) break; + result = RtlNtStatusToDosError(unload_driver( entry, FALSE )); + break; + + default: + FIXME( "got driver ctrl %x for %s\n", ctrl, wine_dbgstr_w(driver_name) ); + break; + } + LeaveCriticalSection( &drivers_cs ); + return result; +} + static DWORD WINAPI service_handler( DWORD ctrl, DWORD event_type, LPVOID event_data, LPVOID context ) { - const WCHAR *driver_name = context; + const WCHAR *service_group = context; + + if (ctrl & SERVICE_CONTROL_FORWARD_FLAG) + { + if (!event_data) return ERROR_INVALID_PARAMETER; + return device_handler( ctrl & ~SERVICE_CONTROL_FORWARD_FLAG, (const WCHAR *)event_data ); + } switch (ctrl) { case SERVICE_CONTROL_STOP: case SERVICE_CONTROL_SHUTDOWN: - WINE_TRACE( "shutting down %s\n", wine_dbgstr_w(driver_name) ); + TRACE( "shutting down %s\n", wine_dbgstr_w(service_group) ); set_service_status( service_handle, SERVICE_STOP_PENDING, 0 ); shutdown_drivers(); return NO_ERROR; default: - WINE_FIXME( "got service ctrl %x for %s\n", ctrl, wine_dbgstr_w(driver_name) ); + FIXME( "got service ctrl %x for %s\n", ctrl, wine_dbgstr_w(service_group) ); set_service_status( service_handle, SERVICE_RUNNING, SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN ); return NO_ERROR; @@ -508,8 +546,7 @@ static DWORD WINAPI service_handler( DWORD ctrl, DWORD event_type, LPVOID event_ static void WINAPI ServiceMain( DWORD argc, LPWSTR *argv ) { - const WCHAR *driver_name = argv[0]; - NTSTATUS status; + const WCHAR *service_group = (argc >= 2) ? argv[1] : argv[0]; if (!(stop_event = CreateEventW( NULL, TRUE, FALSE, NULL ))) return; @@ -517,16 +554,16 @@ static void WINAPI ServiceMain( DWORD argc, LPWSTR *argv ) return; if (!(manager_handle = OpenSCManagerW( NULL, NULL, SC_MANAGER_CONNECT ))) return; - if (!(service_handle = RegisterServiceCtrlHandlerExW( driver_name, service_handler, (void *)driver_name ))) + if (!(service_handle = RegisterServiceCtrlHandlerExW( winedeviceW, service_handler, (void *)service_group ))) return; - EnterCriticalSection( &drivers_cs ); - status = create_driver( driver_name ); - LeaveCriticalSection( &drivers_cs ); + TRACE( "starting service group %s\n", wine_dbgstr_w(service_group) ); + set_service_status( service_handle, SERVICE_RUNNING, + SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN ); - if (status == STATUS_SUCCESS) - wine_ntoskrnl_main_loop( stop_event ); + wine_ntoskrnl_main_loop( stop_event ); + TRACE( "service group %s stopped\n", wine_dbgstr_w(service_group) ); set_service_status( service_handle, SERVICE_STOPPED, 0 ); CloseServiceHandle( manager_handle ); CloseThreadpoolCleanupGroup( cleanup_group ); @@ -537,13 +574,7 @@ int wmain( int argc, WCHAR *argv[] ) { SERVICE_TABLE_ENTRYW service_table[2]; - if (!argv[1]) - { - WINE_ERR( "missing device name, winedevice isn't supposed to be run manually\n" ); - return 1; - } - - service_table[0].lpServiceName = argv[1]; + service_table[0].lpServiceName = (void *)winedeviceW; service_table[0].lpServiceProc = ServiceMain; service_table[1].lpServiceName = NULL; service_table[1].lpServiceProc = NULL;