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