mirror of
https://gitlab.winehq.org/wine/wine.git
synced 2025-08-29 02:33:58 +02:00
1535 lines
54 KiB
C
1535 lines
54 KiB
C
/*
|
|
* Copyright (C) 2023 Mohamad Al-Jaf
|
|
* Copyright (C) 2025 Vibhav Pant
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
|
|
*/
|
|
|
|
#include "wine/debug.h"
|
|
#include "wine/rbtree.h"
|
|
#include "winreg.h"
|
|
#include "winternl.h"
|
|
#include "cfgmgr32.h"
|
|
#include "winuser.h"
|
|
#include "dbt.h"
|
|
#include "wine/plugplay.h"
|
|
#include "setupapi.h"
|
|
#include "devfiltertypes.h"
|
|
#include "devquery.h"
|
|
|
|
#include "initguid.h"
|
|
#include "devpkey.h"
|
|
|
|
WINE_DEFAULT_DEBUG_CHANNEL(setupapi);
|
|
|
|
/***********************************************************************
|
|
* CM_MapCrToWin32Err (cfgmgr32.@)
|
|
*/
|
|
DWORD WINAPI CM_MapCrToWin32Err( CONFIGRET code, DWORD default_error )
|
|
{
|
|
TRACE( "code: %#lx, default_error: %ld\n", code, default_error );
|
|
|
|
switch (code)
|
|
{
|
|
case CR_SUCCESS: return ERROR_SUCCESS;
|
|
case CR_OUT_OF_MEMORY: return ERROR_NOT_ENOUGH_MEMORY;
|
|
case CR_INVALID_POINTER: return ERROR_INVALID_USER_BUFFER;
|
|
case CR_INVALID_FLAG: return ERROR_INVALID_FLAGS;
|
|
case CR_INVALID_DEVNODE:
|
|
case CR_INVALID_DEVICE_ID:
|
|
case CR_INVALID_MACHINENAME:
|
|
case CR_INVALID_PROPERTY:
|
|
case CR_INVALID_REFERENCE_STRING: return ERROR_INVALID_DATA;
|
|
case CR_NO_SUCH_DEVNODE:
|
|
case CR_NO_SUCH_VALUE:
|
|
case CR_NO_SUCH_DEVICE_INTERFACE: return ERROR_NOT_FOUND;
|
|
case CR_ALREADY_SUCH_DEVNODE: return ERROR_ALREADY_EXISTS;
|
|
case CR_BUFFER_SMALL: return ERROR_INSUFFICIENT_BUFFER;
|
|
case CR_NO_REGISTRY_HANDLE: return ERROR_INVALID_HANDLE;
|
|
case CR_REGISTRY_ERROR: return ERROR_REGISTRY_CORRUPT;
|
|
case CR_NO_SUCH_REGISTRY_KEY: return ERROR_FILE_NOT_FOUND;
|
|
case CR_REMOTE_COMM_FAILURE:
|
|
case CR_MACHINE_UNAVAILABLE:
|
|
case CR_NO_CM_SERVICES: return ERROR_SERVICE_NOT_ACTIVE;
|
|
case CR_ACCESS_DENIED: return ERROR_ACCESS_DENIED;
|
|
case CR_CALL_NOT_IMPLEMENTED: return ERROR_CALL_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
return default_error;
|
|
}
|
|
|
|
struct cm_notify_context
|
|
{
|
|
HDEVNOTIFY notify;
|
|
void *user_data;
|
|
PCM_NOTIFY_CALLBACK callback;
|
|
};
|
|
|
|
CALLBACK DWORD devnotify_callback( HANDLE handle, DWORD flags, DEV_BROADCAST_HDR *header )
|
|
{
|
|
struct cm_notify_context *ctx = handle;
|
|
CM_NOTIFY_EVENT_DATA *event_data;
|
|
CM_NOTIFY_ACTION action;
|
|
DWORD size, ret;
|
|
|
|
TRACE( "(%p, %#lx, %p)\n", handle, flags, header );
|
|
|
|
switch (flags)
|
|
{
|
|
case DBT_DEVICEARRIVAL:
|
|
action = CM_NOTIFY_ACTION_DEVICEINTERFACEARRIVAL;
|
|
break;
|
|
case DBT_DEVICEREMOVECOMPLETE:
|
|
FIXME( "CM_NOTIFY_ACTION_DEVICEREMOVECOMPLETE not implemented\n" );
|
|
action = CM_NOTIFY_ACTION_DEVICEINTERFACEREMOVAL;
|
|
break;
|
|
case DBT_CUSTOMEVENT:
|
|
action = CM_NOTIFY_ACTION_DEVICECUSTOMEVENT;
|
|
break;
|
|
default:
|
|
FIXME( "Unexpected flags value: %#lx\n", flags );
|
|
return 0;
|
|
}
|
|
|
|
switch (header->dbch_devicetype)
|
|
{
|
|
case DBT_DEVTYP_DEVICEINTERFACE:
|
|
{
|
|
const DEV_BROADCAST_DEVICEINTERFACE_W *iface = (DEV_BROADCAST_DEVICEINTERFACE_W *)header;
|
|
UINT data_size = wcslen( iface->dbcc_name ) + 1;
|
|
|
|
size = offsetof( CM_NOTIFY_EVENT_DATA, u.DeviceInterface.SymbolicLink[data_size] );
|
|
if (!(event_data = calloc( 1, size ))) return 0;
|
|
|
|
event_data->FilterType = CM_NOTIFY_FILTER_TYPE_DEVICEINTERFACE;
|
|
event_data->u.DeviceInterface.ClassGuid = iface->dbcc_classguid;
|
|
memcpy( event_data->u.DeviceInterface.SymbolicLink, iface->dbcc_name, data_size * sizeof(WCHAR) );
|
|
break;
|
|
}
|
|
case DBT_DEVTYP_HANDLE:
|
|
{
|
|
const DEV_BROADCAST_HANDLE *handle = (DEV_BROADCAST_HANDLE *)header;
|
|
UINT data_size = handle->dbch_size - 2 * sizeof(WCHAR) - offsetof( DEV_BROADCAST_HANDLE, dbch_data );
|
|
|
|
size = offsetof( CM_NOTIFY_EVENT_DATA, u.DeviceHandle.Data[data_size] );
|
|
if (!(event_data = calloc( 1, size ))) return 0;
|
|
|
|
event_data->FilterType = CM_NOTIFY_FILTER_TYPE_DEVICEHANDLE;
|
|
event_data->u.DeviceHandle.EventGuid = handle->dbch_eventguid;
|
|
event_data->u.DeviceHandle.NameOffset = handle->dbch_nameoffset;
|
|
event_data->u.DeviceHandle.DataSize = data_size;
|
|
memcpy( event_data->u.DeviceHandle.Data, handle->dbch_data, data_size );
|
|
break;
|
|
}
|
|
default:
|
|
FIXME( "Unexpected devicetype value: %#lx\n", header->dbch_devicetype );
|
|
return 0;
|
|
}
|
|
|
|
ret = ctx->callback( ctx, ctx->user_data, action, event_data, size );
|
|
free( event_data );
|
|
return ret;
|
|
}
|
|
|
|
static const char *debugstr_CM_NOTIFY_FILTER( const CM_NOTIFY_FILTER *filter )
|
|
{
|
|
if (!filter) return "(null)";
|
|
switch (filter->FilterType)
|
|
{
|
|
case CM_NOTIFY_FILTER_TYPE_DEVICEINTERFACE:
|
|
return wine_dbg_sprintf( "{%#lx %lx CM_NOTIFY_FILTER_TYPE_DEVICEINTERFACE %lu {{%s}}}", filter->cbSize,
|
|
filter->Flags, filter->Reserved,
|
|
debugstr_guid( &filter->u.DeviceInterface.ClassGuid ) );
|
|
case CM_NOTIFY_FILTER_TYPE_DEVICEHANDLE:
|
|
return wine_dbg_sprintf( "{%#lx %lx CM_NOTIFY_FILTER_TYPE_DEVICEHANDLE %lu {{%p}}}", filter->cbSize,
|
|
filter->Flags, filter->Reserved, filter->u.DeviceHandle.hTarget );
|
|
case CM_NOTIFY_FILTER_TYPE_DEVICEINSTANCE:
|
|
return wine_dbg_sprintf( "{%#lx %lx CM_NOTIFY_FILTER_TYPE_DEVICEINSTANCE %lu {{%s}}}", filter->cbSize,
|
|
filter->Flags, filter->Reserved, debugstr_w( filter->u.DeviceInstance.InstanceId ) );
|
|
default:
|
|
return wine_dbg_sprintf( "{%#lx %lx (unknown FilterType %d) %lu}", filter->cbSize, filter->Flags,
|
|
filter->FilterType, filter->Reserved );
|
|
}
|
|
}
|
|
|
|
static CONFIGRET create_notify_context( const CM_NOTIFY_FILTER *filter, HCMNOTIFICATION *notify_handle,
|
|
PCM_NOTIFY_CALLBACK callback, void *user_data )
|
|
{
|
|
union
|
|
{
|
|
DEV_BROADCAST_HDR header;
|
|
DEV_BROADCAST_DEVICEINTERFACE_W iface;
|
|
DEV_BROADCAST_HANDLE handle;
|
|
} notify_filter = {0};
|
|
struct cm_notify_context *ctx;
|
|
static const GUID GUID_NULL;
|
|
|
|
switch (filter->FilterType)
|
|
{
|
|
case CM_NOTIFY_FILTER_TYPE_DEVICEINTERFACE:
|
|
notify_filter.iface.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
|
|
if (filter->Flags & CM_NOTIFY_FILTER_FLAG_ALL_INTERFACE_CLASSES)
|
|
{
|
|
if (!IsEqualGUID( &filter->u.DeviceInterface.ClassGuid, &GUID_NULL )) return CR_INVALID_DATA;
|
|
notify_filter.iface.dbcc_size = offsetof( DEV_BROADCAST_DEVICEINTERFACE_W, dbcc_classguid );
|
|
}
|
|
else
|
|
{
|
|
notify_filter.iface.dbcc_size = offsetof( DEV_BROADCAST_DEVICEINTERFACE_W, dbcc_name );
|
|
notify_filter.iface.dbcc_classguid = filter->u.DeviceInterface.ClassGuid;
|
|
}
|
|
break;
|
|
case CM_NOTIFY_FILTER_TYPE_DEVICEHANDLE:
|
|
notify_filter.handle.dbch_devicetype = DBT_DEVTYP_HANDLE;
|
|
notify_filter.handle.dbch_size = sizeof(notify_filter.handle);
|
|
notify_filter.handle.dbch_handle = filter->u.DeviceHandle.hTarget;
|
|
break;
|
|
case CM_NOTIFY_FILTER_TYPE_DEVICEINSTANCE:
|
|
FIXME( "CM_NOTIFY_FILTER_TYPE_DEVICEINSTANCE is not supported!\n" );
|
|
return CR_CALL_NOT_IMPLEMENTED;
|
|
default:
|
|
return CR_INVALID_DATA;
|
|
}
|
|
|
|
if (!(ctx = calloc( 1, sizeof(*ctx) ))) return CR_OUT_OF_MEMORY;
|
|
|
|
ctx->user_data = user_data;
|
|
ctx->callback = callback;
|
|
if (!(ctx->notify = I_ScRegisterDeviceNotification( ctx, ¬ify_filter.header, devnotify_callback )))
|
|
{
|
|
free( ctx );
|
|
switch (GetLastError())
|
|
{
|
|
case ERROR_NOT_ENOUGH_MEMORY: return CR_OUT_OF_MEMORY;
|
|
case ERROR_INVALID_PARAMETER: return CR_INVALID_DATA;
|
|
default: return CR_FAILURE;
|
|
}
|
|
}
|
|
*notify_handle = ctx;
|
|
return CR_SUCCESS;
|
|
}
|
|
|
|
/***********************************************************************
|
|
* CM_Register_Notification (cfgmgr32.@)
|
|
*/
|
|
CONFIGRET WINAPI CM_Register_Notification( CM_NOTIFY_FILTER *filter, void *context,
|
|
PCM_NOTIFY_CALLBACK callback, HCMNOTIFICATION *notify_context )
|
|
{
|
|
TRACE( "(%s %p %p %p)\n", debugstr_CM_NOTIFY_FILTER( filter ), context, callback, notify_context );
|
|
|
|
if (!notify_context) return CR_FAILURE;
|
|
if (!filter || !callback || filter->cbSize != sizeof(*filter)) return CR_INVALID_DATA;
|
|
|
|
return create_notify_context( filter, notify_context, callback, context );
|
|
}
|
|
|
|
/***********************************************************************
|
|
* CM_Unregister_Notification (cfgmgr32.@)
|
|
*/
|
|
CONFIGRET WINAPI CM_Unregister_Notification( HCMNOTIFICATION notify )
|
|
{
|
|
struct cm_notify_context *ctx = notify;
|
|
|
|
TRACE( "(%p)\n", notify );
|
|
|
|
if (!notify) return CR_INVALID_DATA;
|
|
|
|
I_ScUnregisterDeviceNotification( ctx->notify );
|
|
free( ctx );
|
|
|
|
return CR_SUCCESS;
|
|
}
|
|
|
|
/***********************************************************************
|
|
* CM_Get_Device_Interface_PropertyW (cfgmgr32.@)
|
|
*/
|
|
CONFIGRET WINAPI CM_Get_Device_Interface_PropertyW( LPCWSTR device_interface, const DEVPROPKEY *property_key,
|
|
DEVPROPTYPE *property_type, BYTE *property_buffer,
|
|
ULONG *property_buffer_size, ULONG flags )
|
|
{
|
|
SP_DEVICE_INTERFACE_DATA iface = {sizeof(iface)};
|
|
HDEVINFO set;
|
|
DWORD err;
|
|
BOOL ret;
|
|
|
|
TRACE( "%s %p %p %p %p %ld.\n", debugstr_w(device_interface), property_key, property_type, property_buffer,
|
|
property_buffer_size, flags);
|
|
|
|
if (!property_key) return CR_FAILURE;
|
|
if (!device_interface || !property_type || !property_buffer_size) return CR_INVALID_POINTER;
|
|
if (*property_buffer_size && !property_buffer) return CR_INVALID_POINTER;
|
|
if (flags) return CR_INVALID_FLAG;
|
|
|
|
set = SetupDiCreateDeviceInfoListExW( NULL, NULL, NULL, NULL );
|
|
if (set == INVALID_HANDLE_VALUE) return CR_OUT_OF_MEMORY;
|
|
if (!SetupDiOpenDeviceInterfaceW( set, device_interface, 0, &iface ))
|
|
{
|
|
SetupDiDestroyDeviceInfoList( set );
|
|
TRACE( "No interface %s, err %lu.\n", debugstr_w( device_interface ), GetLastError());
|
|
return CR_NO_SUCH_DEVICE_INTERFACE;
|
|
}
|
|
|
|
ret = SetupDiGetDeviceInterfacePropertyW( set, &iface, property_key, property_type, property_buffer,
|
|
*property_buffer_size, property_buffer_size, 0 );
|
|
err = ret ? 0 : GetLastError();
|
|
SetupDiDestroyDeviceInfoList( set );
|
|
switch (err)
|
|
{
|
|
case ERROR_SUCCESS:
|
|
return CR_SUCCESS;
|
|
case ERROR_INSUFFICIENT_BUFFER:
|
|
return CR_BUFFER_SMALL;
|
|
case ERROR_NOT_FOUND:
|
|
return CR_NO_SUCH_VALUE;
|
|
default:
|
|
return CR_FAILURE;
|
|
}
|
|
}
|
|
|
|
static BOOL dev_properties_append( DEVPROPERTY **properties, ULONG *props_len, const DEVPROPKEY *key, DEVPROPTYPE type,
|
|
ULONG buf_size, void *buf )
|
|
{
|
|
DEVPROPERTY *tmp;
|
|
|
|
if (!(tmp = realloc( *properties, (*props_len + 1) * sizeof( **properties ))))
|
|
return FALSE;
|
|
*properties = tmp;
|
|
|
|
tmp = &tmp[*props_len];
|
|
tmp->CompKey.Key = *key;
|
|
tmp->CompKey.Store = DEVPROP_STORE_SYSTEM;
|
|
tmp->CompKey.LocaleName = NULL;
|
|
tmp->Type = type;
|
|
tmp->BufferSize = buf_size;
|
|
tmp->Buffer = buf;
|
|
|
|
*props_len += 1;
|
|
return TRUE;
|
|
}
|
|
|
|
static const char *debugstr_DEVPROPKEY( const DEVPROPKEY *key )
|
|
{
|
|
if (!key) return "(null)";
|
|
return wine_dbg_sprintf( "{%s, %04lx}", debugstr_guid( &key->fmtid ), key->pid );
|
|
}
|
|
|
|
static const char *debugstr_DEVPROPCOMPKEY( const DEVPROPCOMPKEY *key )
|
|
{
|
|
if (!key) return "(null)";
|
|
return wine_dbg_sprintf( "{%s, %d, %s}", debugstr_DEVPROPKEY( &key->Key ), key->Store,
|
|
debugstr_w( key->LocaleName ) );
|
|
}
|
|
|
|
static const char *debugstr_DEV_OBJECT( const DEV_OBJECT *obj )
|
|
{
|
|
if (!obj) return "(null)";
|
|
return wine_dbg_sprintf( "{%d, %s, %lu, %p}", obj->ObjectType, debugstr_w( obj->pszObjectId ), obj->cPropertyCount,
|
|
obj->pProperties );
|
|
}
|
|
|
|
static int devproperty_compare( const void *p1, const void *p2 )
|
|
{
|
|
const DEVPROPCOMPKEY *key1 = &((DEVPROPERTY *)p1)->CompKey;
|
|
const DEVPROPCOMPKEY *key2 = &((DEVPROPERTY *)p2)->CompKey;
|
|
int cmp = memcmp( key1, key2, offsetof( DEVPROPCOMPKEY, LocaleName ));
|
|
|
|
if (cmp)
|
|
return cmp;
|
|
if (key1->LocaleName == key2->LocaleName)
|
|
return 0;
|
|
if (!key1->LocaleName)
|
|
return -1;
|
|
if (!key2->LocaleName)
|
|
return 1;
|
|
return wcsicmp( key1->LocaleName, key2->LocaleName );
|
|
}
|
|
|
|
static const char *debugstr_DEVPROP_OPERATOR( DEVPROP_OPERATOR op )
|
|
{
|
|
DWORD compare = op & DEVPROP_OPERATOR_MASK_EVAL;
|
|
DWORD list = op & DEVPROP_OPERATOR_MASK_LIST;
|
|
DWORD modifier = op & DEVPROP_OPERATOR_MASK_MODIFIER;
|
|
DWORD logical = op & DEVPROP_OPERATOR_MASK_LOGICAL;
|
|
DWORD array = op & DEVPROP_OPERATOR_MASK_ARRAY;
|
|
|
|
return wine_dbg_sprintf( "(%#lx|%#lx|%#lx|%#lx|%#lx)", list, array, modifier, compare, logical );
|
|
}
|
|
|
|
|
|
static const char *debugstr_DEVPROP_FILTER_EXPRESSION( const DEVPROP_FILTER_EXPRESSION *filter )
|
|
{
|
|
if (!filter) return "(null)";
|
|
return wine_dbg_sprintf( "{%s, {%s, %#lx, %lu, %p}}", debugstr_DEVPROP_OPERATOR( filter->Operator ),
|
|
debugstr_DEVPROPCOMPKEY( &filter->Property.CompKey ), filter->Property.Type,
|
|
filter->Property.BufferSize, filter->Property.Buffer );
|
|
}
|
|
|
|
/* Evaluate a filter expression containing comparison operator. */
|
|
static HRESULT devprop_filter_eval_compare( const DEV_OBJECT *obj, const DEVPROP_FILTER_EXPRESSION *filter )
|
|
{
|
|
const DEVPROPERTY *cmp_prop = &filter->Property;
|
|
DEVPROP_OPERATOR op = filter->Operator;
|
|
const DEVPROPERTY *prop = NULL;
|
|
BOOL ret = FALSE;
|
|
|
|
TRACE( "(%s, %s)\n", debugstr_DEV_OBJECT( obj ), debugstr_DEVPROP_FILTER_EXPRESSION( filter ) );
|
|
|
|
if ((op & DEVPROP_OPERATOR_MASK_MODIFIER) & ~(DEVPROP_OPERATOR_MODIFIER_NOT | DEVPROP_OPERATOR_MODIFIER_IGNORE_CASE))
|
|
return E_INVALIDARG;
|
|
|
|
switch (filter->Operator & DEVPROP_OPERATOR_MASK_EVAL)
|
|
{
|
|
case DEVPROP_OPERATOR_EXISTS:
|
|
prop = bsearch( &filter->Property.CompKey, obj->pProperties, obj->cPropertyCount, sizeof( *obj->pProperties ),
|
|
devproperty_compare );
|
|
ret = prop && prop->Type != DEVPROP_TYPE_EMPTY;
|
|
break;
|
|
case DEVPROP_OPERATOR_EQUALS:
|
|
case DEVPROP_OPERATOR_LESS_THAN:
|
|
case DEVPROP_OPERATOR_GREATER_THAN:
|
|
case DEVPROP_OPERATOR_LESS_THAN_EQUALS:
|
|
case DEVPROP_OPERATOR_GREATER_THAN_EQUALS:
|
|
{
|
|
int cmp = 0;
|
|
|
|
prop = bsearch( &filter->Property.CompKey, obj->pProperties, obj->cPropertyCount, sizeof( *obj->pProperties ),
|
|
devproperty_compare );
|
|
if (prop && cmp_prop->Type == prop->Type && cmp_prop->BufferSize == prop->BufferSize)
|
|
{
|
|
switch (prop->Type)
|
|
{
|
|
case DEVPROP_TYPE_STRING:
|
|
cmp = op & DEVPROP_OPERATOR_MODIFIER_IGNORE_CASE ? wcsicmp( prop->Buffer, cmp_prop->Buffer )
|
|
: wcscmp( prop->Buffer, cmp_prop->Buffer );
|
|
break;
|
|
default:
|
|
cmp = memcmp( prop->Buffer, cmp_prop->Buffer, prop->BufferSize );
|
|
break;
|
|
}
|
|
if (op == DEVPROP_OPERATOR_EQUALS)
|
|
ret = !cmp;
|
|
else if (op & DEVPROP_OPERATOR_EQUALS && !cmp)
|
|
ret = TRUE;
|
|
else
|
|
ret = (op & DEVPROP_OPERATOR_LESS_THAN) ? cmp < 0 : cmp > 0;
|
|
}
|
|
break;
|
|
}
|
|
case DEVPROP_OPERATOR_BITWISE_AND:
|
|
case DEVPROP_OPERATOR_BITWISE_OR:
|
|
case DEVPROP_OPERATOR_BEGINS_WITH:
|
|
case DEVPROP_OPERATOR_ENDS_WITH:
|
|
case DEVPROP_OPERATOR_CONTAINS:
|
|
default:
|
|
FIXME( "Unsupported operator: %s", debugstr_DEVPROP_OPERATOR( filter->Operator & DEVPROP_OPERATOR_MASK_EVAL ) );
|
|
return S_OK;
|
|
}
|
|
|
|
if (op & DEVPROP_OPERATOR_MODIFIER_NOT)
|
|
ret = !ret;
|
|
return ret ? S_OK : S_FALSE;
|
|
}
|
|
|
|
/* Return S_OK if the specified filter expressions match the object, S_FALSE if it doesn't. */
|
|
static HRESULT devprop_filter_matches_object( const DEV_OBJECT *obj, ULONG filters_len,
|
|
const DEVPROP_FILTER_EXPRESSION *filters )
|
|
{
|
|
HRESULT hr = S_OK;
|
|
ULONG i;
|
|
|
|
TRACE( "(%s, %lu, %p)\n", debugstr_DEV_OBJECT( obj ), filters_len, filters );
|
|
|
|
if (!filters_len)
|
|
return S_OK;
|
|
|
|
/* By default, the evaluation is performed by AND-ing all individual filter expressions. */
|
|
for (i = 0; i < filters_len; i++)
|
|
{
|
|
const DEVPROP_FILTER_EXPRESSION *filter = &filters[i];
|
|
DEVPROP_OPERATOR op = filter->Operator;
|
|
|
|
if (op == DEVPROP_OPERATOR_NONE)
|
|
{
|
|
hr = S_FALSE;
|
|
break;
|
|
}
|
|
if (op & (DEVPROP_OPERATOR_MASK_LIST | DEVPROP_OPERATOR_MASK_ARRAY))
|
|
{
|
|
FIXME( "Unsupported list/array operator: %s\n", debugstr_DEVPROP_OPERATOR( op ) );
|
|
continue;
|
|
}
|
|
if (op & DEVPROP_OPERATOR_MASK_LOGICAL)
|
|
{
|
|
FIXME( "Unsupported logical operator: %s\n", debugstr_DEVPROP_OPERATOR( op ) );
|
|
continue;
|
|
}
|
|
if (op & DEVPROP_OPERATOR_MASK_EVAL)
|
|
{
|
|
hr = devprop_filter_eval_compare( obj, filter );
|
|
if (FAILED( hr ) || hr == S_FALSE)
|
|
break;
|
|
}
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
static HRESULT stack_push( DEVPROP_OPERATOR **stack, ULONG *len, DEVPROP_OPERATOR op )
|
|
{
|
|
DEVPROP_OPERATOR *tmp;
|
|
|
|
if (!(tmp = realloc( *stack, (*len + 1) * sizeof( op ) )))
|
|
return E_OUTOFMEMORY;
|
|
*stack = tmp;
|
|
tmp[*len] = op;
|
|
*len += 1;
|
|
return S_OK;
|
|
}
|
|
|
|
static DEVPROP_OPERATOR stack_pop( DEVPROP_OPERATOR **stack, ULONG *len )
|
|
{
|
|
DEVPROP_OPERATOR op = DEVPROP_OPERATOR_NONE;
|
|
|
|
if (*len)
|
|
{
|
|
op = (*stack)[*len - 1];
|
|
*len -= 1;
|
|
}
|
|
return op;
|
|
}
|
|
|
|
static BOOL devprop_type_validate( DEVPROPTYPE type, ULONG buf_size )
|
|
{
|
|
static const DWORD type_size[] = {
|
|
0, 0,
|
|
sizeof( BYTE ), sizeof( BYTE ),
|
|
sizeof( INT16 ), sizeof( INT16 ),
|
|
sizeof( INT32 ), sizeof( INT32 ),
|
|
sizeof( INT64 ), sizeof( INT64 ),
|
|
sizeof( FLOAT ), sizeof( DOUBLE ), sizeof( DECIMAL ),
|
|
sizeof( GUID ),
|
|
sizeof( CURRENCY ),
|
|
sizeof( DATE ),
|
|
sizeof( FILETIME ),
|
|
sizeof( DEVPROP_BOOLEAN ),
|
|
[DEVPROP_TYPE_DEVPROPKEY] = sizeof( DEVPROPKEY ),
|
|
[DEVPROP_TYPE_DEVPROPTYPE] = sizeof( DEVPROPTYPE ),
|
|
[DEVPROP_TYPE_ERROR] = sizeof( ULONG ),
|
|
[DEVPROP_TYPE_NTSTATUS] = sizeof( NTSTATUS )
|
|
};
|
|
DWORD mod = type & DEVPROP_MASK_TYPEMOD, size;
|
|
|
|
if (mod && mod != DEVPROP_TYPEMOD_ARRAY && mod != DEVPROP_TYPEMOD_LIST)
|
|
{
|
|
FIXME( "Unknown DEVPROPTYPE value: %#lx\n", type );
|
|
return FALSE;
|
|
}
|
|
|
|
switch (type & DEVPROP_MASK_TYPE)
|
|
{
|
|
case DEVPROP_TYPE_EMPTY:
|
|
case DEVPROP_TYPE_NULL:
|
|
return !mod;
|
|
case DEVPROP_TYPE_SECURITY_DESCRIPTOR:
|
|
case DEVPROP_TYPE_STRING_INDIRECT:
|
|
return !mod && !!buf_size;
|
|
|
|
case DEVPROP_TYPE_STRING:
|
|
case DEVPROP_TYPE_SECURITY_DESCRIPTOR_STRING:
|
|
return mod != DEVPROP_TYPEMOD_ARRAY && !!buf_size;
|
|
default:
|
|
/* The only valid modifier for the remaining types is DEVPROP_TYPEMOD_ARRAY */
|
|
if ((type & DEVPROP_MASK_TYPE) > DEVPROP_TYPE_NTSTATUS ||
|
|
(mod && mod != DEVPROP_TYPEMOD_ARRAY))
|
|
{
|
|
FIXME( "Unknown DEVPROPTYPE value: %#lx\n", type );
|
|
return FALSE;
|
|
}
|
|
size = type_size[type & DEVPROP_MASK_TYPE];
|
|
}
|
|
|
|
return mod == DEVPROP_TYPEMOD_ARRAY ? buf_size >= size : buf_size == size;
|
|
}
|
|
|
|
static HRESULT devprop_filters_validate( ULONG filters_len, const DEVPROP_FILTER_EXPRESSION *filters )
|
|
{
|
|
DEVPROP_OPERATOR *stack = NULL;
|
|
ULONG i, logical_open = 0, stack_top = 0;
|
|
HRESULT hr = S_OK;
|
|
|
|
for (i = 0; i < filters_len; i++)
|
|
{
|
|
const DEVPROP_FILTER_EXPRESSION *filter = &filters[i];
|
|
const DEVPROPERTY *prop = &filter->Property;
|
|
DEVPROP_OPERATOR op = filter->Operator;
|
|
DWORD compare = op & DEVPROP_OPERATOR_MASK_EVAL;
|
|
DWORD list = op & DEVPROP_OPERATOR_MASK_LIST;
|
|
DWORD modifier = op & DEVPROP_OPERATOR_MASK_MODIFIER;
|
|
DWORD logical = op & DEVPROP_OPERATOR_MASK_LOGICAL;
|
|
DWORD array = op & DEVPROP_OPERATOR_MASK_ARRAY;
|
|
|
|
if ((compare && compare > DEVPROP_OPERATOR_CONTAINS)
|
|
|| (logical && (op & DEVPROP_OPERATOR_MASK_NOT_LOGICAL))
|
|
|| (array && (op != DEVPROP_OPERATOR_ARRAY_CONTAINS))
|
|
|| !!prop->Buffer != !!prop->BufferSize)
|
|
{
|
|
FIXME( "Unknown operator: %#x\n", op );
|
|
hr = E_INVALIDARG;
|
|
break;
|
|
}
|
|
if (!op) continue;
|
|
if (compare && compare != DEVPROP_OPERATOR_EXISTS
|
|
&& !devprop_type_validate( prop->Type, prop->BufferSize ))
|
|
{
|
|
hr = E_INVALIDARG;
|
|
break;
|
|
}
|
|
|
|
switch (modifier)
|
|
{
|
|
case DEVPROP_OPERATOR_NONE:
|
|
case DEVPROP_OPERATOR_MODIFIER_NOT:
|
|
case DEVPROP_OPERATOR_MODIFIER_IGNORE_CASE:
|
|
break;
|
|
default:
|
|
hr = E_INVALIDARG;
|
|
break;
|
|
}
|
|
|
|
switch (list)
|
|
{
|
|
case DEVPROP_OPERATOR_NONE:
|
|
case DEVPROP_OPERATOR_LIST_CONTAINS:
|
|
case DEVPROP_OPERATOR_LIST_ELEMENT_BEGINS_WITH:
|
|
case DEVPROP_OPERATOR_LIST_ELEMENT_ENDS_WITH:
|
|
case DEVPROP_OPERATOR_LIST_ELEMENT_CONTAINS:
|
|
break;
|
|
default:
|
|
hr = E_INVALIDARG;
|
|
break;
|
|
}
|
|
|
|
switch (logical)
|
|
{
|
|
case DEVPROP_OPERATOR_NONE:
|
|
break;
|
|
case DEVPROP_OPERATOR_AND_OPEN:
|
|
case DEVPROP_OPERATOR_OR_OPEN:
|
|
case DEVPROP_OPERATOR_NOT_OPEN:
|
|
hr = stack_push( &stack, &stack_top, logical );
|
|
logical_open = i;
|
|
break;
|
|
case DEVPROP_OPERATOR_AND_CLOSE:
|
|
case DEVPROP_OPERATOR_OR_CLOSE:
|
|
case DEVPROP_OPERATOR_NOT_CLOSE:
|
|
{
|
|
DEVPROP_OPERATOR top = stack_pop( &stack, &stack_top );
|
|
/* The operator should be correct paired, and shouldn't be empty. */
|
|
if (logical - top != (DEVPROP_OPERATOR_AND_CLOSE - DEVPROP_OPERATOR_AND_OPEN) || logical_open == i - 1)
|
|
hr = E_INVALIDARG;
|
|
break;
|
|
}
|
|
default:
|
|
hr = E_INVALIDARG;
|
|
break;
|
|
}
|
|
|
|
if (FAILED( hr )) break;
|
|
}
|
|
|
|
if (stack_top)
|
|
hr = E_INVALIDARG;
|
|
free( stack );
|
|
return hr;
|
|
}
|
|
|
|
static HRESULT dev_object_iface_get_props( DEV_OBJECT *obj, HDEVINFO set, SP_DEVICE_INTERFACE_DATA *iface_data,
|
|
ULONG props_len, const DEVPROPCOMPKEY *props, BOOL all_props, BOOL sort )
|
|
{
|
|
DEVPROPKEY *all_keys = NULL;
|
|
DWORD keys_len = 0, i = 0;
|
|
HRESULT hr = S_OK;
|
|
|
|
obj->cPropertyCount = 0;
|
|
obj->pProperties = NULL;
|
|
if (!props && !all_props)
|
|
return S_OK;
|
|
|
|
if (all_props)
|
|
{
|
|
DWORD req = 0;
|
|
if (SetupDiGetDeviceInterfacePropertyKeys( set, iface_data, NULL, 0, &req, 0 )
|
|
|| GetLastError() != ERROR_INSUFFICIENT_BUFFER)
|
|
return HRESULT_FROM_WIN32( GetLastError() );
|
|
|
|
keys_len = req;
|
|
if (!(all_keys = calloc( keys_len, sizeof( *all_keys ) )))
|
|
return E_OUTOFMEMORY;
|
|
if (!SetupDiGetDeviceInterfacePropertyKeys( set, iface_data, all_keys, keys_len, &req, 0 ))
|
|
{
|
|
free( all_keys );
|
|
return HRESULT_FROM_WIN32( GetLastError() );
|
|
}
|
|
}
|
|
else
|
|
keys_len = props_len;
|
|
|
|
for (i = 0; i < keys_len; i++)
|
|
{
|
|
const DEVPROPKEY *key = all_keys ? &all_keys[i] : &props[i].Key;
|
|
DWORD req = 0, size;
|
|
DEVPROPTYPE type;
|
|
BYTE *buf;
|
|
|
|
if (props && props[i].Store != DEVPROP_STORE_SYSTEM)
|
|
{
|
|
FIXME( "Unsupported Store value: %d\n", props[i].Store );
|
|
continue;
|
|
}
|
|
if (SetupDiGetDeviceInterfacePropertyW( set, iface_data, key, &type, NULL, 0, &req, 0 )
|
|
|| GetLastError() != ERROR_INSUFFICIENT_BUFFER)
|
|
{
|
|
if (props && !dev_properties_append( (DEVPROPERTY **)&obj->pProperties, &obj->cPropertyCount, key,
|
|
DEVPROP_TYPE_EMPTY, 0, NULL ))
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
goto done;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
size = req;
|
|
if (!(buf = calloc( 1, size )))
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
goto done;
|
|
}
|
|
if (!SetupDiGetDeviceInterfacePropertyW( set, iface_data, key, &type, buf, size, &req, 0 ))
|
|
{
|
|
hr = HRESULT_FROM_WIN32( GetLastError() );
|
|
free( buf );
|
|
goto done;
|
|
}
|
|
if (!dev_properties_append( (DEVPROPERTY **)&obj->pProperties, &obj->cPropertyCount, key, type, size, buf ))
|
|
{
|
|
free( buf );
|
|
hr = E_OUTOFMEMORY;
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
done:
|
|
free( all_keys );
|
|
if (FAILED( hr ))
|
|
{
|
|
DevFreeObjectProperties( obj->cPropertyCount, obj->pProperties );
|
|
obj->cPropertyCount = 0;
|
|
obj->pProperties = NULL;
|
|
}
|
|
else if (sort) /* Sort properties by DEVPROPCOMPKEY for faster filter evaluation. */
|
|
qsort( (DEVPROPERTY *)obj->pProperties, obj->cPropertyCount, sizeof( *obj->pProperties ), devproperty_compare );
|
|
return hr;
|
|
}
|
|
|
|
typedef HRESULT (*enum_device_object_cb)( DEV_OBJECT object, void *context );
|
|
|
|
static void dev_object_remove_unwanted_props( DEV_OBJECT *obj, ULONG keys_len, const DEVPROPCOMPKEY *keys_wanted )
|
|
{
|
|
DEVPROPERTY *props = (DEVPROPERTY *)obj->pProperties;
|
|
ULONG i = 0, j;
|
|
|
|
if (!keys_len)
|
|
{
|
|
DevFreeObjectProperties( obj->cPropertyCount, obj->pProperties );
|
|
obj->cPropertyCount = 0;
|
|
obj->pProperties = NULL;
|
|
}
|
|
|
|
while (i < obj->cPropertyCount)
|
|
{
|
|
BOOL found = FALSE;
|
|
|
|
for (j = 0; j < keys_len; j++)
|
|
{
|
|
if (IsEqualDevPropCompKey( props[i].CompKey, keys_wanted[j] ))
|
|
{
|
|
found = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
if (!found)
|
|
{
|
|
free( obj->pProperties[i].Buffer );
|
|
props[i] = props[obj->cPropertyCount - 1];
|
|
obj->cPropertyCount--;
|
|
}
|
|
else
|
|
i++;
|
|
}
|
|
}
|
|
|
|
static HRESULT enum_dev_objects( DEV_OBJECT_TYPE type, ULONG props_len, const DEVPROPCOMPKEY *props, BOOL all_props,
|
|
ULONG filters_len, const DEVPROP_FILTER_EXPRESSION *filters,
|
|
enum_device_object_cb callback, void *data )
|
|
{
|
|
HKEY iface_key;
|
|
HRESULT hr = S_OK;
|
|
|
|
DWORD i;
|
|
|
|
switch (type)
|
|
{
|
|
case DevObjectTypeDeviceInterface:
|
|
case DevObjectTypeDeviceInterfaceDisplay:
|
|
break;
|
|
case DevObjectTypeDeviceContainer:
|
|
case DevObjectTypeDevice:
|
|
case DevObjectTypeDeviceInterfaceClass:
|
|
case DevObjectTypeAEP:
|
|
case DevObjectTypeAEPContainer:
|
|
case DevObjectTypeDeviceInstallerClass:
|
|
case DevObjectTypeDeviceContainerDisplay:
|
|
case DevObjectTypeAEPService:
|
|
case DevObjectTypeDevicePanel:
|
|
case DevObjectTypeAEPProtocol:
|
|
FIXME("Unsupported DEV_OJBECT_TYPE: %d\n", type );
|
|
default:
|
|
return S_OK;
|
|
}
|
|
|
|
if (!(iface_key = SetupDiOpenClassRegKeyExW( NULL, KEY_ENUMERATE_SUB_KEYS, DIOCR_INTERFACE, NULL, NULL )))
|
|
return HRESULT_FROM_WIN32( GetLastError() );
|
|
|
|
for (i = 0; SUCCEEDED( hr ); i++)
|
|
{
|
|
char buffer[sizeof( SP_DEVICE_INTERFACE_DETAIL_DATA_W ) + MAX_PATH * sizeof( WCHAR )];
|
|
SP_DEVICE_INTERFACE_DATA iface = {.cbSize = sizeof( iface )};
|
|
SP_DEVICE_INTERFACE_DETAIL_DATA_W *detail = (void *)buffer;
|
|
HDEVINFO set = INVALID_HANDLE_VALUE;
|
|
WCHAR iface_guid_str[40];
|
|
DWORD ret, len, j;
|
|
GUID iface_class;
|
|
|
|
len = ARRAY_SIZE( iface_guid_str );
|
|
ret = RegEnumKeyExW( iface_key, i, iface_guid_str, &len, NULL, NULL, NULL, NULL );
|
|
if (ret)
|
|
{
|
|
hr = (ret == ERROR_NO_MORE_ITEMS) ? S_OK : HRESULT_FROM_WIN32( ret );
|
|
break;
|
|
}
|
|
|
|
iface_guid_str[37] = '\0';
|
|
if (!UuidFromStringW( &iface_guid_str[1], &iface_class ))
|
|
{
|
|
set = SetupDiGetClassDevsW( &iface_class, NULL, NULL, DIGCF_DEVICEINTERFACE );
|
|
if (set == INVALID_HANDLE_VALUE) hr = HRESULT_FROM_WIN32( GetLastError() );
|
|
}
|
|
else
|
|
{
|
|
ERR( "Could not parse device interface GUID %s\n", debugstr_w( iface_guid_str ) );
|
|
continue;
|
|
}
|
|
|
|
for (j = 0; SUCCEEDED( hr ) && SetupDiEnumDeviceInterfaces( set, NULL, &iface_class, j, &iface ); j++)
|
|
{
|
|
DEV_OBJECT obj = {0};
|
|
|
|
detail->cbSize = sizeof( *detail );
|
|
if (!SetupDiGetDeviceInterfaceDetailW( set, &iface, detail, sizeof( buffer ), NULL, NULL )) continue;
|
|
|
|
obj.ObjectType = type;
|
|
obj.pszObjectId = detail->DevicePath;
|
|
/* If we're also filtering objects, get all properties for this object. Once the filters have been
|
|
* evaluated, free properties that have not been requested, and set cPropertyCount to props_len. */
|
|
if (filters_len)
|
|
hr = dev_object_iface_get_props( &obj, set, &iface, 0, NULL, TRUE, TRUE );
|
|
else
|
|
hr = dev_object_iface_get_props( &obj, set, &iface, props_len, props, all_props, FALSE );
|
|
if (SUCCEEDED( hr ))
|
|
{
|
|
if (filters_len)
|
|
{
|
|
hr = devprop_filter_matches_object( &obj, filters_len, filters );
|
|
/* Shrink pProperties to only the desired ones, unless DevQueryFlagAllProperties is set. */
|
|
if (!all_props)
|
|
dev_object_remove_unwanted_props( &obj, props_len, props );
|
|
}
|
|
if (hr == S_OK)
|
|
hr = callback( obj, data );
|
|
else
|
|
DevFreeObjectProperties( obj.cPropertyCount, obj.pProperties );
|
|
}
|
|
}
|
|
|
|
if (set != INVALID_HANDLE_VALUE)
|
|
SetupDiDestroyDeviceInfoList( set );
|
|
}
|
|
RegCloseKey( iface_key );
|
|
return SUCCEEDED( hr ) ? S_OK : hr;
|
|
}
|
|
|
|
struct objects_list
|
|
{
|
|
DEV_OBJECT *objects;
|
|
ULONG len;
|
|
};
|
|
|
|
static HRESULT dev_objects_append( DEV_OBJECT obj, void *data )
|
|
{
|
|
struct objects_list *objects = data;
|
|
WCHAR *id;
|
|
DEV_OBJECT *tmp;
|
|
|
|
if (!(id = wcsdup( obj.pszObjectId )))
|
|
return E_OUTOFMEMORY;
|
|
if (!(tmp = realloc( objects->objects, (objects->len + 1) * sizeof( obj ) )))
|
|
{
|
|
free( id );
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
|
|
objects->objects = tmp;
|
|
tmp = &tmp[objects->len++];
|
|
*tmp = obj;
|
|
tmp->pszObjectId = id;
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT WINAPI DevGetObjects( DEV_OBJECT_TYPE type, ULONG flags, ULONG props_len, const DEVPROPCOMPKEY *props,
|
|
ULONG filters_len, const DEVPROP_FILTER_EXPRESSION *filters, ULONG *objs_len,
|
|
const DEV_OBJECT **objs )
|
|
{
|
|
TRACE( "(%d, %#lx, %lu, %p, %lu, %p, %p, %p)\n", type, flags, props_len, props, filters_len, filters, objs_len, objs );
|
|
return DevGetObjectsEx( type, flags, props_len, props, filters_len, filters, 0, NULL, objs_len, objs );
|
|
}
|
|
|
|
HRESULT WINAPI DevGetObjectsEx( DEV_OBJECT_TYPE type, ULONG flags, ULONG props_len, const DEVPROPCOMPKEY *props,
|
|
ULONG filters_len, const DEVPROP_FILTER_EXPRESSION *filters, ULONG params_len,
|
|
const DEV_QUERY_PARAMETER *params, ULONG *objs_len, const DEV_OBJECT **objs )
|
|
{
|
|
ULONG valid_flags = DevQueryFlagAllProperties | DevQueryFlagLocalize;
|
|
struct objects_list objects = {0};
|
|
HRESULT hr = S_OK;
|
|
|
|
TRACE( "(%d, %#lx, %lu, %p, %lu, %p, %lu, %p, %p, %p)\n", type, flags, props_len, props, filters_len, filters,
|
|
params_len, params, objs_len, objs );
|
|
|
|
if (!!props_len != !!props || !!filters_len != !!filters || !!params_len != !!params || (flags & ~valid_flags)
|
|
|| (props_len && (flags & DevQueryFlagAllProperties))
|
|
|| FAILED( devprop_filters_validate( filters_len, filters ) ))
|
|
return E_INVALIDARG;
|
|
if (params)
|
|
FIXME( "Query parameters are not supported!\n" );
|
|
|
|
*objs = NULL;
|
|
*objs_len = 0;
|
|
|
|
hr = enum_dev_objects( type, props_len, props, !!(flags & DevQueryFlagAllProperties), filters_len, filters,
|
|
dev_objects_append, &objects );
|
|
if (SUCCEEDED( hr ))
|
|
{
|
|
*objs = objects.objects;
|
|
*objs_len = objects.len;
|
|
}
|
|
else
|
|
DevFreeObjects( objects.len, objects.objects );
|
|
|
|
return hr;
|
|
}
|
|
|
|
void WINAPI DevFreeObjects( ULONG objs_len, const DEV_OBJECT *objs )
|
|
{
|
|
DEV_OBJECT *objects = (DEV_OBJECT *)objs;
|
|
ULONG i;
|
|
|
|
TRACE( "(%lu, %p)\n", objs_len, objs );
|
|
|
|
for (i = 0; i < objs_len; i++)
|
|
{
|
|
DevFreeObjectProperties( objects[i].cPropertyCount, objects[i].pProperties );
|
|
free( (void *)objects[i].pszObjectId );
|
|
}
|
|
free( objects );
|
|
return;
|
|
}
|
|
|
|
struct device_iface_path
|
|
{
|
|
struct rb_entry entry;
|
|
WCHAR *path;
|
|
};
|
|
|
|
static int device_iface_path_cmp( const void *key, const struct rb_entry *entry )
|
|
{
|
|
const struct device_iface_path *iface = RB_ENTRY_VALUE( entry, struct device_iface_path, entry );
|
|
return wcsicmp( key, iface->path );
|
|
}
|
|
|
|
struct device_query_context
|
|
{
|
|
LONG ref;
|
|
DEV_OBJECT_TYPE type;
|
|
ULONG flags;
|
|
ULONG prop_keys_len;
|
|
DEVPROPCOMPKEY *prop_keys;
|
|
BOOL filters;
|
|
|
|
CRITICAL_SECTION cs;
|
|
PDEV_QUERY_RESULT_CALLBACK callback;
|
|
void *user_data;
|
|
DEV_QUERY_STATE state;
|
|
HANDLE closed;
|
|
struct rb_tree known_ifaces;
|
|
HCMNOTIFICATION notify;
|
|
};
|
|
|
|
static HRESULT device_query_context_add_object( DEV_OBJECT obj, void *data )
|
|
{
|
|
DEV_QUERY_RESULT_ACTION_DATA action_data = {0};
|
|
struct device_query_context *ctx = data;
|
|
struct device_iface_path *iface_entry = NULL;
|
|
HRESULT hr = S_OK;
|
|
|
|
TRACE( "(%s, %p)\n", debugstr_w( obj.pszObjectId ), data );
|
|
|
|
action_data.Action = DevQueryResultAdd;
|
|
action_data.Data.DeviceObject = obj;
|
|
ctx->callback( (HDEVQUERY)ctx, ctx->user_data, &action_data );
|
|
|
|
EnterCriticalSection( &ctx->cs );
|
|
if (ctx->state == DevQueryStateClosed)
|
|
hr = E_CHANGED_STATE;
|
|
else if (obj.ObjectType == DevObjectTypeDeviceInterface || obj.ObjectType == DevObjectTypeDeviceInterfaceDisplay)
|
|
{
|
|
if (!(iface_entry = calloc( 1, sizeof( *iface_entry ) )) || !(iface_entry->path = wcsdup( obj.pszObjectId )))
|
|
{
|
|
if (iface_entry) free( iface_entry->path );
|
|
free( iface_entry );
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
else if (rb_put( &ctx->known_ifaces, iface_entry->path, &iface_entry->entry ))
|
|
{
|
|
free( iface_entry->path );
|
|
free( iface_entry );
|
|
}
|
|
}
|
|
LeaveCriticalSection( &ctx->cs );
|
|
|
|
DevFreeObjectProperties( obj.cPropertyCount, obj.pProperties );
|
|
return hr;
|
|
}
|
|
|
|
static HRESULT device_query_context_create( struct device_query_context **query, DEV_OBJECT_TYPE type, ULONG flags,
|
|
ULONG props_len, const DEVPROPCOMPKEY *props,
|
|
PDEV_QUERY_RESULT_CALLBACK callback, void *user_data )
|
|
{
|
|
struct device_query_context *ctx;
|
|
ULONG i;
|
|
|
|
if (!(ctx = calloc( 1, sizeof( *ctx ))))
|
|
return E_OUTOFMEMORY;
|
|
ctx->ref = 1;
|
|
if (!(flags & DevQueryFlagAsyncClose))
|
|
{
|
|
ctx->closed = CreateEventW( NULL, FALSE, FALSE, NULL );
|
|
if (ctx->closed == INVALID_HANDLE_VALUE)
|
|
{
|
|
free( ctx );
|
|
return HRESULT_FROM_WIN32( GetLastError() );
|
|
}
|
|
}
|
|
ctx->prop_keys_len = props_len;
|
|
if (props_len && !(ctx->prop_keys = calloc( props_len, sizeof( *props ) )))
|
|
{
|
|
if (ctx->closed) CloseHandle( ctx->closed );
|
|
free( ctx );
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
for (i = 0; i < props_len; i++)
|
|
{
|
|
ctx->prop_keys[i].Key = props[i].Key;
|
|
ctx->prop_keys[i].Store = props[i].Store;
|
|
}
|
|
InitializeCriticalSectionEx( &ctx->cs, 0, RTL_CRITICAL_SECTION_FLAG_FORCE_DEBUG_INFO );
|
|
ctx->cs.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": device_query_context.cs");
|
|
ctx->type = type;
|
|
ctx->flags = flags;
|
|
ctx->callback = callback;
|
|
ctx->user_data = user_data;
|
|
ctx->state = DevQueryStateInitialized;
|
|
rb_init( &ctx->known_ifaces, device_iface_path_cmp );
|
|
|
|
*query = ctx;
|
|
return S_OK;
|
|
}
|
|
|
|
static void device_query_context_addref( struct device_query_context *ctx )
|
|
{
|
|
InterlockedIncrement( &ctx->ref );
|
|
}
|
|
|
|
static void device_iface_path_free( struct rb_entry *entry, void *data )
|
|
{
|
|
struct device_iface_path *path = RB_ENTRY_VALUE( entry, struct device_iface_path, entry );
|
|
free( path->path );
|
|
free( path );
|
|
}
|
|
|
|
static void device_query_context_release( struct device_query_context *ctx )
|
|
{
|
|
if (!InterlockedDecrement( &ctx->ref ))
|
|
{
|
|
free( ctx->prop_keys );
|
|
ctx->cs.DebugInfo->Spare[0] = 0;
|
|
DeleteCriticalSection( &ctx->cs );
|
|
if (ctx->closed) CloseHandle( ctx->closed );
|
|
rb_destroy( &ctx->known_ifaces, device_iface_path_free, NULL );
|
|
free( ctx );
|
|
}
|
|
}
|
|
|
|
static void device_query_context_notify_state_change( struct device_query_context *ctx, DEV_QUERY_STATE state )
|
|
{
|
|
DEV_QUERY_RESULT_ACTION_DATA action_data = {0};
|
|
|
|
action_data.Action = DevQueryResultStateChange;
|
|
action_data.Data.State = state;
|
|
ctx->callback( (HDEVQUERY)ctx, ctx->user_data, &action_data );
|
|
}
|
|
|
|
static void CALLBACK device_query_context_notify_enum_completed_async( TP_CALLBACK_INSTANCE *instance, void *data )
|
|
{
|
|
device_query_context_notify_state_change( data, DevQueryStateEnumCompleted );
|
|
device_query_context_release( data );
|
|
}
|
|
|
|
static void CALLBACK device_query_context_notify_closed_async( TP_CALLBACK_INSTANCE *instance, void *data )
|
|
{
|
|
device_query_context_notify_state_change( data, DevQueryStateClosed );
|
|
device_query_context_release( data );
|
|
}
|
|
|
|
static void CALLBACK device_query_context_notify_aborted_async( TP_CALLBACK_INSTANCE *instance, void *data )
|
|
{
|
|
device_query_context_notify_state_change( data, DevQueryStateAborted );
|
|
device_query_context_release( data );
|
|
}
|
|
|
|
struct devquery_notify_data
|
|
{
|
|
DEV_QUERY_RESULT_ACTION_DATA action_data;
|
|
struct device_query_context *ctx;
|
|
};
|
|
|
|
static void CALLBACK device_query_notify_dev_async( TP_CALLBACK_INSTANCE *instance, void *notify_data )
|
|
{
|
|
struct devquery_notify_data *data = notify_data;
|
|
|
|
data->ctx->callback( (HDEVQUERY)data->ctx, data->ctx->user_data, &data->action_data );
|
|
if (data->action_data.Action != DevQueryResultStateChange)
|
|
DevFreeObjectProperties( data->action_data.Data.DeviceObject.cPropertyCount,
|
|
data->action_data.Data.DeviceObject.pProperties );
|
|
device_query_context_release( data->ctx );
|
|
free( data );
|
|
}
|
|
|
|
static const char *debugstr_CM_NOTIFY_ACTION( CM_NOTIFY_ACTION action )
|
|
{
|
|
static const char *str[] = {
|
|
"CM_NOTIFY_ACTION_DEVICEINTERFACEARRIVAL",
|
|
"CM_NOTIFY_ACTION_DEVICEINTERFACEREMOVAL",
|
|
"CM_NOTIFY_ACTION_DEVICEQUERYREMOVE",
|
|
"CM_NOTIFY_ACTION_DEVICEQUERYREMOVEFAILED",
|
|
"CM_NOTIFY_ACTION_DEVICEREMOVEPENDING",
|
|
"CM_NOTIFY_ACTION_DEVICEREMOVECOMPLETE",
|
|
"CM_NOTIFY_ACTION_DEVICECUSTOMEVENT",
|
|
"CM_NOTIFY_ACTION_DEVICEINSTANCEENUMERATED",
|
|
"CM_NOTIFY_ACTION_DEVICEINSTANCESTARTED",
|
|
"CM_NOTIFY_ACTION_DEVICEINSTANCEREMOVED",
|
|
};
|
|
|
|
return action < ARRAY_SIZE( str ) ? str[action] : wine_dbg_sprintf( "(unknown %d)", action );
|
|
}
|
|
|
|
static const char *debugstr_CM_NOTIFY_EVENT_DATA( const CM_NOTIFY_EVENT_DATA *event )
|
|
{
|
|
if (!event) return wine_dbg_sprintf( "(null)" );
|
|
switch (event->FilterType)
|
|
{
|
|
case CM_NOTIFY_FILTER_TYPE_DEVICEINTERFACE:
|
|
return wine_dbg_sprintf( "{CM_NOTIFY_FILTER_TYPE_DEVICEINTERFACE %lu {{%s %s}}}", event->Reserved,
|
|
debugstr_guid( &event->u.DeviceInterface.ClassGuid ),
|
|
debugstr_w( event->u.DeviceInterface.SymbolicLink ) );
|
|
case CM_NOTIFY_FILTER_TYPE_DEVICEHANDLE:
|
|
if (event->u.DeviceHandle.NameOffset == -1)
|
|
{
|
|
return wine_dbg_sprintf( "{CM_NOTIFY_FILTER_TYPE_DEVICEHANDLE %lu {{%s -1 %lu %p}}}", event->Reserved,
|
|
debugstr_guid( &event->u.DeviceHandle.EventGuid ),
|
|
event->u.DeviceHandle.DataSize, event->u.DeviceHandle.Data );
|
|
}
|
|
return wine_dbg_sprintf( "{CM_NOTIFY_FILTER_TYPE_DEVICEHANDLE %lu {{%s %ld %lu %p %s}}}", event->Reserved,
|
|
debugstr_guid( &event->u.DeviceHandle.EventGuid ), event->u.DeviceHandle.NameOffset,
|
|
event->u.DeviceHandle.DataSize, event->u.DeviceHandle.Data,
|
|
debugstr_w( (WCHAR *)&event->u.DeviceHandle.Data[event->u.DeviceHandle.NameOffset] ) );
|
|
case CM_NOTIFY_FILTER_TYPE_DEVICEINSTANCE:
|
|
return wine_dbg_sprintf( "{CM_NOTIFY_FILTER_TYPE_DEVICEINSTANCE %lu %s}", event->Reserved,
|
|
debugstr_w( event->u.DeviceInstance.InstanceId ) );
|
|
default:
|
|
return wine_dbg_sprintf( "{(unknown %d) %lu}", event->FilterType, event->Reserved );
|
|
}
|
|
}
|
|
|
|
static DWORD CALLBACK device_query_context_cm_notify_callback( HCMNOTIFICATION notify, void *data,
|
|
CM_NOTIFY_ACTION action,
|
|
CM_NOTIFY_EVENT_DATA *event, DWORD event_size )
|
|
{
|
|
struct device_query_context *ctx = data;
|
|
const ULONG prop_flags = ctx->flags & (DevQueryFlagAllProperties | DevQueryFlagLocalize);
|
|
DEV_QUERY_RESULT_ACTION_DATA *action_data;
|
|
struct devquery_notify_data *notify_data;
|
|
struct device_iface_path *iface_entry;
|
|
const WCHAR *iface_path;
|
|
struct rb_entry *entry;
|
|
HRESULT hr = S_OK;
|
|
|
|
TRACE( "(%p, %p, %s, %s, %lu)\n", notify, data, debugstr_CM_NOTIFY_ACTION( action ),
|
|
debugstr_CM_NOTIFY_EVENT_DATA( event ), event_size );
|
|
|
|
if (action != CM_NOTIFY_ACTION_DEVICEINTERFACEARRIVAL && action != CM_NOTIFY_ACTION_DEVICEINTERFACEREMOVAL)
|
|
{
|
|
FIXME( "Unexpected CM_NOTIFY_ACTION value: %d\n", action );
|
|
return 0;
|
|
}
|
|
|
|
iface_path = event->u.DeviceInterface.SymbolicLink;
|
|
EnterCriticalSection( &ctx->cs );
|
|
if (ctx->state == DevQueryStateClosed || ctx->state == DevQueryStateAborted)
|
|
{
|
|
LeaveCriticalSection( &ctx->cs );
|
|
return 0;
|
|
}
|
|
|
|
if (!(notify_data = calloc( 1, sizeof ( *notify_data ) )))
|
|
goto abort;
|
|
action_data = ¬ify_data->action_data;
|
|
notify_data->ctx = ctx;
|
|
action_data->Data.DeviceObject.ObjectType = ctx->type;
|
|
|
|
/* Interface removal and arrival for objects that have already been enumerated. */
|
|
if ((entry = rb_get( &ctx->known_ifaces, iface_path )))
|
|
{
|
|
const DEVPROP_BOOLEAN enabled = action == CM_NOTIFY_ACTION_DEVICEINTERFACEARRIVAL ? DEVPROP_TRUE : DEVPROP_FALSE;
|
|
DEVPROPERTY *prop;
|
|
|
|
if (!(prop = calloc( 1, sizeof( *prop ))))
|
|
goto abort;
|
|
if (!(prop->Buffer = calloc( 1, sizeof( enabled ) )))
|
|
{
|
|
free( prop );
|
|
goto abort;
|
|
}
|
|
prop->CompKey.Key = DEVPKEY_DeviceInterface_Enabled;
|
|
prop->Type = DEVPROP_TYPE_BOOLEAN;
|
|
prop->BufferSize = sizeof( enabled );
|
|
memcpy( prop->Buffer, &enabled, sizeof( enabled ) );
|
|
iface_entry = RB_ENTRY_VALUE( entry, struct device_iface_path, entry );
|
|
action_data->Action = DevQueryResultUpdate;
|
|
action_data->Data.DeviceObject.cPropertyCount = 1;
|
|
action_data->Data.DeviceObject.pProperties = prop;
|
|
}
|
|
else
|
|
{
|
|
if (!(iface_entry = calloc( 1, sizeof( *iface_entry ) )))
|
|
goto abort;
|
|
if (!(iface_entry->path = wcsdup( iface_path )))
|
|
{
|
|
free( iface_entry );
|
|
goto abort;
|
|
}
|
|
rb_put( &ctx->known_ifaces, iface_path, &iface_entry->entry );
|
|
hr = DevGetObjectProperties( ctx->type, iface_path, prop_flags, ctx->prop_keys_len, ctx->prop_keys,
|
|
&action_data->Data.DeviceObject.cPropertyCount,
|
|
&action_data->Data.DeviceObject.pProperties );
|
|
if (FAILED( hr ))
|
|
goto abort;
|
|
action_data->Action = DevQueryResultAdd;
|
|
}
|
|
|
|
action_data->Data.DeviceObject.pszObjectId = iface_entry->path;
|
|
device_query_context_addref( ctx );
|
|
if (!TrySubmitThreadpoolCallback( device_query_notify_dev_async, notify_data, NULL ))
|
|
device_query_context_release( ctx );
|
|
LeaveCriticalSection( &ctx->cs );
|
|
return 0;
|
|
abort:
|
|
free( notify_data );
|
|
ctx->state = DevQueryStateAborted;
|
|
device_query_context_addref( ctx );
|
|
if (!TrySubmitThreadpoolCallback( device_query_context_notify_aborted_async, ctx, NULL ))
|
|
device_query_context_release( ctx );
|
|
LeaveCriticalSection( &ctx->cs );
|
|
return 0;
|
|
}
|
|
|
|
static void CALLBACK device_query_enum_objects_async( TP_CALLBACK_INSTANCE *instance, void *data )
|
|
{
|
|
struct device_query_context *ctx = data;
|
|
BOOL success;
|
|
HRESULT hr = S_OK;
|
|
|
|
if (!ctx->filters)
|
|
hr = enum_dev_objects( ctx->type, ctx->prop_keys_len, ctx->prop_keys, !!(ctx->flags & DevQueryFlagAllProperties),
|
|
0, NULL, device_query_context_add_object, ctx );
|
|
|
|
EnterCriticalSection( &ctx->cs );
|
|
if (ctx->state == DevQueryStateClosed)
|
|
hr = E_CHANGED_STATE;
|
|
|
|
switch (hr)
|
|
{
|
|
case S_OK:
|
|
ctx->state = DevQueryStateEnumCompleted;
|
|
success = TrySubmitThreadpoolCallback( device_query_context_notify_enum_completed_async, ctx, NULL );
|
|
if (ctx->filters || !(ctx->flags & DevQueryFlagUpdateResults))
|
|
break;
|
|
switch (ctx->type)
|
|
{
|
|
case DevObjectTypeDeviceInterface:
|
|
case DevObjectTypeDeviceInterfaceDisplay:
|
|
{
|
|
CM_NOTIFY_FILTER filter = { 0 };
|
|
CONFIGRET ret;
|
|
|
|
filter.cbSize = sizeof( filter );
|
|
filter.Flags = CM_NOTIFY_FILTER_FLAG_ALL_INTERFACE_CLASSES;
|
|
filter.FilterType = CM_NOTIFY_FILTER_TYPE_DEVICEINTERFACE;
|
|
device_query_context_addref( ctx );
|
|
ret = CM_Register_Notification( &filter, ctx, device_query_context_cm_notify_callback, &ctx->notify );
|
|
if (ret)
|
|
{
|
|
ERR( "CM_Register_Notification failed: %lu\n", ret );
|
|
device_query_context_release( ctx );
|
|
goto abort;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
FIXME( "Device updates not supported for object type %d\n", ctx->type );
|
|
break;
|
|
}
|
|
break;
|
|
case E_CHANGED_STATE:
|
|
if (!(ctx->flags & DevQueryFlagAsyncClose))
|
|
{
|
|
LeaveCriticalSection( &ctx->cs );
|
|
SetEvent( ctx->closed );
|
|
device_query_context_release( ctx );
|
|
return;
|
|
}
|
|
success = TrySubmitThreadpoolCallback( device_query_context_notify_closed_async, ctx, NULL );
|
|
break;
|
|
default:
|
|
goto abort;
|
|
}
|
|
|
|
LeaveCriticalSection( &ctx->cs );
|
|
if (!success)
|
|
device_query_context_release( ctx );
|
|
return;
|
|
abort:
|
|
ctx->state = DevQueryStateAborted;
|
|
success = TrySubmitThreadpoolCallback( device_query_context_notify_aborted_async, ctx, NULL );
|
|
LeaveCriticalSection( &ctx->cs );
|
|
if (!success)
|
|
device_query_context_release( ctx );
|
|
}
|
|
|
|
HRESULT WINAPI DevCreateObjectQuery( DEV_OBJECT_TYPE type, ULONG flags, ULONG props_len, const DEVPROPCOMPKEY *props,
|
|
ULONG filters_len, const DEVPROP_FILTER_EXPRESSION *filters,
|
|
PDEV_QUERY_RESULT_CALLBACK callback, void *user_data, HDEVQUERY *devquery )
|
|
{
|
|
TRACE( "(%d, %#lx, %lu, %p, %lu, %p, %p, %p, %p)\n", type, flags, props_len, props, filters_len, filters, callback,
|
|
user_data, devquery );
|
|
return DevCreateObjectQueryEx( type, flags, props_len, props, filters_len, filters, 0, NULL, callback, user_data,
|
|
devquery );
|
|
}
|
|
|
|
HRESULT WINAPI DevCreateObjectQueryEx( DEV_OBJECT_TYPE type, ULONG flags, ULONG props_len, const DEVPROPCOMPKEY *props,
|
|
ULONG filters_len, const DEVPROP_FILTER_EXPRESSION *filters, ULONG params_len,
|
|
const DEV_QUERY_PARAMETER *params, PDEV_QUERY_RESULT_CALLBACK callback,
|
|
void *user_data, HDEVQUERY *devquery )
|
|
{
|
|
ULONG valid_flags = DevQueryFlagUpdateResults | DevQueryFlagAllProperties | DevQueryFlagLocalize | DevQueryFlagAsyncClose;
|
|
struct device_query_context *ctx = NULL;
|
|
HRESULT hr;
|
|
|
|
TRACE( "(%d, %#lx, %lu, %p, %lu, %p, %lu, %p, %p, %p, %p)\n", type, flags, props_len, props, filters_len,
|
|
filters, params_len, params, callback, user_data, devquery );
|
|
|
|
if (!!props_len != !!props || !!filters_len != !!filters || !!params_len != !!params || (flags & ~valid_flags) || !callback
|
|
|| (props_len && (flags & DevQueryFlagAllProperties))
|
|
|| FAILED( devprop_filters_validate( filters_len, filters ) ))
|
|
return E_INVALIDARG;
|
|
if (filters)
|
|
FIXME( "Query filters are not supported!\n" );
|
|
if (params)
|
|
FIXME( "Query parameters are not supported!\n" );
|
|
|
|
hr = device_query_context_create( &ctx, type, flags, props_len, props, callback, user_data );
|
|
if (FAILED( hr ))
|
|
return hr;
|
|
|
|
ctx->filters = !!filters;
|
|
device_query_context_addref( ctx );
|
|
if (!TrySubmitThreadpoolCallback( device_query_enum_objects_async, ctx, NULL ))
|
|
hr = HRESULT_FROM_WIN32( GetLastError() );
|
|
if (FAILED( hr ))
|
|
{
|
|
device_query_context_release( ctx );
|
|
ctx = NULL;
|
|
}
|
|
|
|
*devquery = (HDEVQUERY)ctx;
|
|
return hr;
|
|
}
|
|
|
|
void WINAPI DevCloseObjectQuery( HDEVQUERY query )
|
|
{
|
|
struct device_query_context *ctx = (struct device_query_context *)query;
|
|
BOOL async = ctx->flags & DevQueryFlagAsyncClose;
|
|
DEV_QUERY_STATE old;
|
|
|
|
TRACE( "(%p)\n", query );
|
|
|
|
if (!query)
|
|
return;
|
|
|
|
EnterCriticalSection( &ctx->cs );
|
|
old = ctx->state;
|
|
ctx->state = DevQueryStateClosed;
|
|
|
|
if (ctx->notify)
|
|
{
|
|
CM_Unregister_Notification( ctx->notify );
|
|
device_query_context_release( ctx ); /* Reference held by CM_Register_Notification. */
|
|
}
|
|
if (async && old == DevQueryStateEnumCompleted)
|
|
{
|
|
/* No asynchronous operation involving this query is active, so we need to notify DevQueryStateClosed. */
|
|
BOOL success = TrySubmitThreadpoolCallback( device_query_context_notify_closed_async, ctx, NULL );
|
|
LeaveCriticalSection( &ctx->cs );
|
|
if (success)
|
|
return;
|
|
}
|
|
else if (!async && old == DevQueryStateInitialized)
|
|
{
|
|
LeaveCriticalSection( &ctx->cs );
|
|
/* Wait for the active async operation to end. */
|
|
WaitForSingleObject( ctx->closed, INFINITE );
|
|
}
|
|
else
|
|
LeaveCriticalSection( &ctx->cs );
|
|
|
|
device_query_context_release( ctx );
|
|
return;
|
|
}
|
|
|
|
HRESULT WINAPI DevGetObjectProperties( DEV_OBJECT_TYPE type, const WCHAR *id, ULONG flags, ULONG props_len,
|
|
const DEVPROPCOMPKEY *props, ULONG *buf_len, const DEVPROPERTY **buf )
|
|
{
|
|
TRACE( "(%d, %s, %#lx, %lu, %p, %p, %p)\n", type, debugstr_w( id ), flags, props_len, props, buf_len, buf );
|
|
return DevGetObjectPropertiesEx( type, id, flags, props_len, props, 0, NULL, buf_len, buf );
|
|
}
|
|
|
|
HRESULT WINAPI DevGetObjectPropertiesEx( DEV_OBJECT_TYPE type, const WCHAR *id, ULONG flags, ULONG props_len,
|
|
const DEVPROPCOMPKEY *props, ULONG params_len,
|
|
const DEV_QUERY_PARAMETER *params, ULONG *buf_len, const DEVPROPERTY **buf )
|
|
{
|
|
HRESULT hr;
|
|
ULONG valid_flags = DevQueryFlagAllProperties | DevQueryFlagLocalize;
|
|
|
|
TRACE( "(%d, %s, %#lx, %lu, %p, %lu, %p, %p, %p)\n", type, debugstr_w( id ), flags, props_len, props,
|
|
params_len, params, buf_len, buf );
|
|
|
|
if (flags & ~valid_flags)
|
|
return E_INVALIDARG;
|
|
if (type == DevObjectTypeUnknown || type > DevObjectTypeAEPProtocol)
|
|
return HRESULT_FROM_WIN32( ERROR_FILE_NOT_FOUND );
|
|
if (!id || !!props_len != !!props || !!params_len != !!params
|
|
|| (props_len && (flags & DevQueryFlagAllProperties)))
|
|
return E_INVALIDARG;
|
|
|
|
switch (type)
|
|
{
|
|
case DevObjectTypeDeviceInterface:
|
|
case DevObjectTypeDeviceInterfaceDisplay:
|
|
{
|
|
SP_DEVICE_INTERFACE_DATA iface = {.cbSize = sizeof( iface )};
|
|
DEV_OBJECT obj = {0};
|
|
HDEVINFO set;
|
|
|
|
set = SetupDiCreateDeviceInfoListExW( NULL, NULL, NULL, NULL );
|
|
if (set == INVALID_HANDLE_VALUE) return HRESULT_FROM_WIN32( GetLastError() );
|
|
if (!SetupDiOpenDeviceInterfaceW( set, id, 0, &iface ))
|
|
{
|
|
DWORD err = GetLastError();
|
|
SetupDiDestroyDeviceInfoList( set );
|
|
return HRESULT_FROM_WIN32(err == ERROR_NO_SUCH_DEVICE_INTERFACE ? ERROR_FILE_NOT_FOUND : err);
|
|
}
|
|
|
|
hr = dev_object_iface_get_props( &obj, set, &iface, props_len, props, !!(flags & DevQueryFlagAllProperties), FALSE );
|
|
*buf = obj.pProperties;
|
|
*buf_len = obj.cPropertyCount;
|
|
break;
|
|
}
|
|
default:
|
|
FIXME( "Unsupported DEV_OBJECT_TYPE: %d\n", type );
|
|
hr = HRESULT_FROM_WIN32( ERROR_FILE_NOT_FOUND );
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
const DEVPROPERTY *WINAPI DevFindProperty( const DEVPROPKEY *key, DEVPROPSTORE store, const WCHAR *locale,
|
|
ULONG props_len, const DEVPROPERTY *props )
|
|
{
|
|
DEVPROPCOMPKEY comp_key;
|
|
ULONG i;
|
|
|
|
TRACE( "(%s, %d, %s, %lu, %p)\n", debugstr_DEVPROPKEY( key ), store, debugstr_w( locale ), props_len, props );
|
|
|
|
/* Windows does not validate parameters here. */
|
|
comp_key.Key = *key;
|
|
comp_key.Store = store;
|
|
comp_key.LocaleName = locale;
|
|
for (i = 0; i < props_len; i++)
|
|
{
|
|
if (IsEqualDevPropCompKey( comp_key, props[i].CompKey ))
|
|
return &props[i];
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void WINAPI DevFreeObjectProperties( ULONG len, const DEVPROPERTY *props )
|
|
{
|
|
DEVPROPERTY *properties = (DEVPROPERTY *)props;
|
|
ULONG i;
|
|
|
|
TRACE( "(%lu, %p)\n", len, props );
|
|
|
|
for (i = 0; i < len; i++)
|
|
free( properties[i].Buffer );
|
|
free( properties );
|
|
}
|