winedevice: Add support for loading/unloading drivers asynchronously.
Aric Stewart
aric at codeweavers.com
Tue Aug 23 13:04:39 CDT 2016
Signed-off-by: Aric Stewart <aric at codeweavers.com>
On 8/23/16 3:13 AM, Sebastian Lackner wrote:
> Signed-off-by: Sebastian Lackner <sebastian at fds-team.de>
> ---
>
> In preparation for the final patch, this adds support for loading/unloading drivers
> asynchronously. With a single driver this doesn't make a big difference, but will
> be required to avoid blocking the service control handler (especially for recursive
> loads). During shutdown a threadpool cleanup group is used to synchronize with any
> pending load/unloads.
>
> This patch also modifies unload_driver to ensure that we never attempt to unload a
> driver without DriverUnload entry point, which could have fatal consequences. On Windows
> it is required to reboot the operating system in such a case to get rid of the driver.
>
> (Unfortunately the diff is a bit bigger because I wanted to avoid forward declarations,
> but I can change that back if preferred.)
>
> programs/winedevice/device.c | 204 ++++++++++++++++++++++++++++++++-----------
> 1 file changed, 152 insertions(+), 52 deletions(-)
>
> diff --git a/programs/winedevice/device.c b/programs/winedevice/device.c
> index ac368c8..e1a38a4 100644
> --- a/programs/winedevice/device.c
> +++ b/programs/winedevice/device.c
> @@ -2,6 +2,7 @@
> * Service process to load a kernel driver
> *
> * Copyright 2007 Alexandre Julliard
> + * Copyright 2016 Sebastian Lackner
> *
> * This library is free software; you can redistribute it and/or
> * modify it under the terms of the GNU Lesser General Public
> @@ -43,7 +44,9 @@ WINE_DECLARE_DEBUG_CHANNEL(relay);
> extern NTSTATUS CDECL wine_ntoskrnl_main_loop( HANDLE stop_event );
>
> static SERVICE_STATUS_HANDLE service_handle;
> +static PTP_CLEANUP_GROUP cleanup_group;
> static SC_HANDLE manager_handle;
> +static BOOL shutdown_in_progress;
> static HANDLE stop_event;
>
> struct wine_driver
> @@ -57,6 +60,15 @@ struct wine_driver
>
> static struct wine_rb_tree wine_drivers;
>
> +static CRITICAL_SECTION drivers_cs;
> +static CRITICAL_SECTION_DEBUG critsect_debug =
> +{
> + 0, 0, &drivers_cs,
> + { &critsect_debug.ProcessLocksList, &critsect_debug.ProcessLocksList },
> + 0, 0, { (DWORD_PTR)(__FILE__ ": drivers_cs") }
> +};
> +static CRITICAL_SECTION drivers_cs = { &critsect_debug, -1, 0, 0, 0, 0 };
> +
> static void *wine_drivers_rb_alloc( size_t size )
> {
> return HeapAlloc( GetProcessHeap(), 0, size );
> @@ -307,32 +319,116 @@ static void set_service_status( SERVICE_STATUS_HANDLE handle, DWORD state, DWORD
> SetServiceStatus( handle, &status );
> }
>
> +static void WINAPI async_unload_driver( PTP_CALLBACK_INSTANCE instance, void *context )
> +{
> + struct wine_driver *driver = context;
> + DRIVER_OBJECT *driver_obj = driver->driver_obj;
> + LDR_MODULE *ldr;
> +
> + if (WINE_TRACE_ON(relay))
> + WINE_DPRINTF( "%04x:Call driver unload %p (obj=%p)\n", GetCurrentThreadId(),
> + driver_obj->DriverUnload, driver_obj );
> +
> + driver_obj->DriverUnload( driver_obj );
> +
> + if (WINE_TRACE_ON(relay))
> + WINE_DPRINTF( "%04x:Ret driver unload %p (obj=%p)\n", GetCurrentThreadId(),
> + driver_obj->DriverUnload, driver_obj );
> +
> + ldr = driver_obj->DriverSection;
> + FreeLibrary( ldr->BaseAddress );
> + IoDeleteDriver( driver_obj );
> + ObDereferenceObject( driver_obj );
> +
> + set_service_status( driver->handle, SERVICE_STOPPED, 0 );
> + CloseServiceHandle( (void *)driver->handle );
> + HeapFree( GetProcessHeap(), 0, driver );
> +}
> +
> /* call the driver unload function */
> -static void unload_driver( struct wine_rb_entry *entry, void *context )
> +static NTSTATUS unload_driver( struct wine_rb_entry *entry, BOOL destroy )
> {
> + TP_CALLBACK_ENVIRON environment;
> struct wine_driver *driver = WINE_RB_ENTRY_VALUE( entry, struct wine_driver, entry );
> DRIVER_OBJECT *driver_obj = driver->driver_obj;
> - LDR_MODULE *ldr = driver_obj->DriverSection;
> +
> + if (!driver_obj)
> + {
> + TRACE( "driver %s has not finished loading yet\n", wine_dbgstr_w(driver->name) );
> + return STATUS_UNSUCCESSFUL;
> + }
> + if (!driver_obj->DriverUnload)
> + {
> + TRACE( "driver %s does not support unloading\n", wine_dbgstr_w(driver->name) );
> + return STATUS_UNSUCCESSFUL;
> + }
>
> TRACE( "stopping driver %s\n", wine_dbgstr_w(driver->name) );
> set_service_status( driver->handle, SERVICE_STOP_PENDING, 0 );
>
> - if (driver_obj->DriverUnload)
> + if (destroy)
> {
> - if (WINE_TRACE_ON(relay))
> - WINE_DPRINTF( "%04x:Call driver unload %p (obj=%p)\n", GetCurrentThreadId(),
> - driver_obj->DriverUnload, driver_obj );
> + async_unload_driver( NULL, driver );
> + return STATUS_SUCCESS;
> + }
>
> - driver_obj->DriverUnload( driver_obj );
> + wine_rb_remove( &wine_drivers, driver->name );
>
> - if (WINE_TRACE_ON(relay))
> - WINE_DPRINTF( "%04x:Ret driver unload %p (obj=%p)\n", GetCurrentThreadId(),
> - driver_obj->DriverUnload, driver_obj );
> + memset( &environment, 0, sizeof(environment) );
> + environment.Version = 1;
> + environment.CleanupGroup = cleanup_group;
> +
> + /* don't block the service control handler */
> + if (!TrySubmitThreadpoolCallback( async_unload_driver, driver, &environment ))
> + async_unload_driver( NULL, driver );
> +
> + return STATUS_SUCCESS;
> +}
> +
> +static void WINAPI async_create_driver( PTP_CALLBACK_INSTANCE instance, void *context )
> +{
> + static const WCHAR driverW[] = {'\\','D','r','i','v','e','r','\\',0};
> + struct wine_driver *driver = context;
> + DRIVER_OBJECT *driver_obj;
> + UNICODE_STRING drv_name;
> + NTSTATUS status;
> + WCHAR *str;
> +
> + if (!(str = HeapAlloc( GetProcessHeap(), 0, sizeof(driverW) + strlenW(driver->name)*sizeof(WCHAR) )))
> + goto error;
> +
> + lstrcpyW( str, driverW);
> + lstrcatW( str, driver->name );
> + RtlInitUnicodeString( &drv_name, str );
> +
> + status = IoCreateDriver( &drv_name, init_driver );
> + if (status != STATUS_SUCCESS)
> + {
> + ERR( "failed to create driver %s: %08x\n", debugstr_w(driver->name), status );
> + RtlFreeUnicodeString( &drv_name );
> + goto error;
> }
>
> - FreeLibrary( ldr->BaseAddress );
> - IoDeleteDriver( driver_obj );
> - ObDereferenceObject( driver_obj );
> + status = ObReferenceObjectByName( &drv_name, OBJ_CASE_INSENSITIVE, NULL,
> + 0, NULL, KernelMode, NULL, (void **)&driver_obj );
> + RtlFreeUnicodeString( &drv_name );
> + if (status != STATUS_SUCCESS)
> + {
> + ERR( "failed to locate driver %s: %08x\n", debugstr_w(driver->name), status );
> + goto error;
> + }
> +
> + EnterCriticalSection( &drivers_cs );
> + driver->driver_obj = driver_obj;
> + set_service_status( driver->handle, SERVICE_RUNNING,
> + SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN );
> + LeaveCriticalSection( &drivers_cs );
> + return;
> +
> +error:
> + EnterCriticalSection( &drivers_cs );
> + wine_rb_remove( &wine_drivers, driver->name );
> + LeaveCriticalSection( &drivers_cs );
>
> set_service_status( driver->handle, SERVICE_STOPPED, 0 );
> CloseServiceHandle( (void *)driver->handle );
> @@ -342,12 +438,9 @@ static void unload_driver( struct wine_rb_entry *entry, void *context )
> /* load a driver and notify services.exe about the status change */
> static NTSTATUS create_driver( const WCHAR *driver_name )
> {
> - static const WCHAR driverW[] = {'\\','D','r','i','v','e','r','\\',0};
> + TP_CALLBACK_ENVIRON environment;
> struct wine_driver *driver;
> - UNICODE_STRING drv_name;
> - NTSTATUS status;
> DWORD length;
> - WCHAR *str;
>
> length = FIELD_OFFSET( struct wine_driver, name[strlenW(driver_name) + 1] );
> if (!(driver = HeapAlloc( GetProcessHeap(), 0, length )))
> @@ -369,45 +462,45 @@ static NTSTATUS create_driver( const WCHAR *driver_name )
> return STATUS_UNSUCCESSFUL;
> }
>
> - if (!(str = HeapAlloc( GetProcessHeap(), 0, sizeof(driverW) + strlenW(driver_name)*sizeof(WCHAR) )))
> - {
> - status = STATUS_NO_MEMORY;
> - goto error;
> - }
> -
> TRACE( "starting driver %s\n", wine_dbgstr_w(driver_name) );
> set_service_status( driver->handle, SERVICE_START_PENDING, 0 );
>
> - lstrcpyW( str, driverW);
> - lstrcatW( str, driver_name );
> - RtlInitUnicodeString( &drv_name, str );
> + memset( &environment, 0, sizeof(environment) );
> + environment.Version = 1;
> + environment.CleanupGroup = cleanup_group;
>
> - status = IoCreateDriver( &drv_name, init_driver );
> - if (status == STATUS_SUCCESS)
> - {
> - status = ObReferenceObjectByName( &drv_name, OBJ_CASE_INSENSITIVE, NULL,
> - 0, NULL, KernelMode, NULL, (void **)&driver->driver_obj );
> - if (status != STATUS_SUCCESS)
> - {
> - ERR("Failed to locate loaded driver (%s)\n", wine_dbgstr_w(driver_name));
> - }
> - }
> + /* don't block the service control handler */
> + if (!TrySubmitThreadpoolCallback( async_create_driver, driver, &environment ))
> + async_create_driver( NULL, driver );
>
> - RtlFreeUnicodeString( &drv_name );
> + return STATUS_SUCCESS;
> +}
>
> - if (status == STATUS_SUCCESS)
> - {
> - set_service_status( driver->handle, SERVICE_RUNNING,
> - SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN );
> - return STATUS_SUCCESS;
> - }
> +static void wine_drivers_rb_destroy( struct wine_rb_entry *entry, void *context )
> +{
> + unload_driver( entry, TRUE );
> +}
>
> -error:
> - set_service_status( driver->handle, SERVICE_STOPPED, 0 );
> - CloseServiceHandle( (void *)driver->handle );
> - wine_rb_remove( &wine_drivers, driver_name );
> - HeapFree( GetProcessHeap(), 0, driver );
> - return status;
> +static void WINAPI async_shutdown_drivers( PTP_CALLBACK_INSTANCE instance, void *context )
> +{
> + CloseThreadpoolCleanupGroupMembers( cleanup_group, FALSE, NULL );
> +
> + EnterCriticalSection( &drivers_cs );
> + wine_rb_destroy( &wine_drivers, wine_drivers_rb_destroy, NULL );
> + LeaveCriticalSection( &drivers_cs );
> +
> + SetEvent( stop_event );
> +}
> +
> +static void shutdown_drivers( void )
> +{
> + if (shutdown_in_progress) return;
> +
> + /* don't block the service control handler */
> + if (!TrySubmitThreadpoolCallback( async_shutdown_drivers, NULL, NULL ))
> + async_shutdown_drivers( NULL, NULL );
> +
> + shutdown_in_progress = TRUE;
> }
>
> static DWORD WINAPI service_handler( DWORD ctrl, DWORD event_type, LPVOID event_data, LPVOID context )
> @@ -420,7 +513,7 @@ static DWORD WINAPI service_handler( DWORD ctrl, DWORD event_type, LPVOID event_
> case SERVICE_CONTROL_SHUTDOWN:
> WINE_TRACE( "shutting down %s\n", wine_dbgstr_w(driver_name) );
> set_service_status( service_handle, SERVICE_STOP_PENDING, 0 );
> - SetEvent( stop_event );
> + shutdown_drivers();
> return NO_ERROR;
> default:
> WINE_FIXME( "got service ctrl %x for %s\n", ctrl, wine_dbgstr_w(driver_name) );
> @@ -433,22 +526,29 @@ 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;
>
> if (wine_rb_init( &wine_drivers, &wine_drivers_rb_functions ))
> return;
> if (!(stop_event = CreateEventW( NULL, TRUE, FALSE, NULL )))
> return;
> + if (!(cleanup_group = CreateThreadpoolCleanupGroup()))
> + return;
> if (!(manager_handle = OpenSCManagerW( NULL, NULL, SC_MANAGER_CONNECT )))
> return;
> if (!(service_handle = RegisterServiceCtrlHandlerExW( driver_name, service_handler, (void *)driver_name )))
> return;
>
> - if (create_driver( driver_name ) == STATUS_SUCCESS)
> + EnterCriticalSection( &drivers_cs );
> + status = create_driver( driver_name );
> + LeaveCriticalSection( &drivers_cs );
> +
> + if (status == STATUS_SUCCESS)
> wine_ntoskrnl_main_loop( stop_event );
>
> - wine_rb_destroy( &wine_drivers, unload_driver, NULL );
> set_service_status( service_handle, SERVICE_STOPPED, 0 );
> CloseServiceHandle( manager_handle );
> + CloseThreadpoolCleanupGroup( cleanup_group );
> CloseHandle( stop_event );
> }
>
>
More information about the wine-patches
mailing list