#include "cfgmgr32_private.h"
#include "devpkey.h"
WINE_DEFAULT_DEBUG_CHANNEL(setupapi);
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 );
}
static BOOL devprop_filter_eval_compare( const DEVPROPERTY *props, UINT props_len, const DEVPROP_FILTER_EXPRESSION *filter )
{
const DEVPROPERTY *cmp_prop = &filter->Property;
DEVPROP_OPERATOR op = filter->Operator;
const DEVPROPERTY *prop = NULL;
BOOL ret = FALSE;
TRACE( "(%p, %u, %s)\n", props, props_len, debugstr_DEVPROP_FILTER_EXPRESSION( filter ) );
switch (filter->Operator & DEVPROP_OPERATOR_MASK_EVAL)
{
case DEVPROP_OPERATOR_EXISTS:
prop = bsearch( &filter->Property.CompKey, props, props_len, sizeof(*props), 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, props, props_len, sizeof(*props), 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 );
TRACE( "%s vs %s -> %u\n", debugstr_w(prop->Buffer), debugstr_w(cmp_prop->Buffer), !!cmp );
break;
case DEVPROP_TYPE_GUID:
if (!(op & DEVPROP_OPERATOR_EQUALS)) break;
default:
cmp = memcmp( prop->Buffer, cmp_prop->Buffer, prop->BufferSize );
break;
}
if (op & DEVPROP_OPERATOR_EQUALS)
ret = !cmp;
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 FALSE;
}
if (op & DEVPROP_OPERATOR_MODIFIER_NOT) ret = !ret;
TRACE( "-> %u\n", ret );
return ret;
}
static const DEVPROP_FILTER_EXPRESSION *find_closing_filter( const DEVPROP_FILTER_EXPRESSION *filter, const DEVPROP_FILTER_EXPRESSION *end )
{
DWORD open = filter->Operator & DEVPROP_OPERATOR_MASK_LOGICAL, close = open + (DEVPROP_OPERATOR_AND_CLOSE - DEVPROP_OPERATOR_AND_OPEN), depth = 0;
for (const DEVPROP_FILTER_EXPRESSION *closing = filter + 1; closing < end; closing++)
{
DWORD logical = closing->Operator & DEVPROP_OPERATOR_MASK_LOGICAL;
if (logical == close && !depth--) return closing;
if (logical == open) depth++;
}
return NULL;
}
static BOOL devprop_filter_matches_properties( const DEVPROPERTY *props, UINT props_len, DEVPROP_OPERATOR op_outer_logical,
const DEVPROP_FILTER_EXPRESSION *filters, const DEVPROP_FILTER_EXPRESSION *end )
{
BOOL ret = TRUE;
TRACE( "(%p, %u, %#x, %p, %p)\n", props, props_len, op_outer_logical, filters, end );
for (const DEVPROP_FILTER_EXPRESSION *filter = filters; filter < end; filter++)
{
DEVPROP_OPERATOR op = filter->Operator;
if (op == DEVPROP_OPERATOR_NONE) ret = FALSE;
else if (op & (DEVPROP_OPERATOR_MASK_LIST | DEVPROP_OPERATOR_MASK_ARRAY))
{
FIXME( "Unsupported list/array operator: %s\n", debugstr_DEVPROP_OPERATOR( op ) );
ret = FALSE;
}
else if (op & DEVPROP_OPERATOR_MASK_LOGICAL)
{
const DEVPROP_FILTER_EXPRESSION *closing = find_closing_filter( filter, end );
ret = devprop_filter_matches_properties( props, props_len, op & DEVPROP_OPERATOR_MASK_LOGICAL, filter + 1, closing );
filter = closing;
}
else if (op & DEVPROP_OPERATOR_MASK_EVAL)
{
ret = devprop_filter_eval_compare( props, props_len, filter );
}
switch (op_outer_logical)
{
case DEVPROP_OPERATOR_NOT_OPEN:
case DEVPROP_OPERATOR_AND_OPEN:
if (!ret) goto done;
break;
case DEVPROP_OPERATOR_OR_OPEN:
if (ret) goto done;
break;
default:
assert( 0 );
break;
}
}
done:
if (op_outer_logical == DEVPROP_OPERATOR_NOT_OPEN) ret = !ret;
TRACE( "-> %u\n", ret );
return ret;
}
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:
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 BOOL devprop_filters_validate( const DEVPROP_FILTER_EXPRESSION *filters, const DEVPROP_FILTER_EXPRESSION *end )
{
const DEVPROP_FILTER_EXPRESSION *closing;
if (filters == end) return FALSE;
for (const DEVPROP_FILTER_EXPRESSION *filter = filters; filter < end; filter++)
{
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 );
return FALSE;
}
if (!op) continue;
if (compare && compare != DEVPROP_OPERATOR_EXISTS
&& !devprop_type_validate( prop->Type, prop->BufferSize ))
return FALSE;
switch (modifier)
{
case DEVPROP_OPERATOR_NONE:
case DEVPROP_OPERATOR_MODIFIER_NOT:
case DEVPROP_OPERATOR_MODIFIER_IGNORE_CASE:
break;
default:
return FALSE;
}
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:
return FALSE;
}
switch (logical)
{
case DEVPROP_OPERATOR_NONE:
break;
case DEVPROP_OPERATOR_AND_OPEN:
case DEVPROP_OPERATOR_OR_OPEN:
case DEVPROP_OPERATOR_NOT_OPEN:
if (!(closing = find_closing_filter( filter, end ))) return FALSE;
if (!devprop_filters_validate( filter + 1, closing )) return FALSE;
filter = closing;
break;
default:
return FALSE;
}
}
return TRUE;
}
static LSTATUS copy_device_interface_property( HKEY hkey, const struct device_interface *iface, DEVPROPERTY *property )
{
struct property prop;
LSTATUS err;
init_property( &prop, &property->CompKey.Key, &property->Type, property->Buffer, &property->BufferSize );
if (!(err = query_device_interface_property( hkey, iface, &prop ))) return ERROR_SUCCESS;
if (err && err != ERROR_MORE_DATA) return ERROR_SUCCESS;
if (!(prop.buffer = malloc( property->BufferSize ))) return E_OUTOFMEMORY;
if ((err = query_device_interface_property( hkey, iface, &prop ))) free( prop.buffer );
else property->Buffer = prop.buffer;
return err;
}
static LSTATUS copy_device_interface_properties( HKEY hkey, const struct device_interface *iface, const DEVPROPCOMPKEY *keys, ULONG keys_len,
DEVPROPERTY *properties, ULONG *props_len )
{
const DEVPROPCOMPKEY *key, *end;
DEVPROPERTY *prop = properties;
LSTATUS err = ERROR_SUCCESS;
for (key = keys, end = keys + keys_len; !err && key < end; key++, prop++)
{
prop->CompKey = *key;
err = copy_device_interface_property( hkey, iface, prop );
}
*props_len = prop - properties;
return err;
}
static LSTATUS get_property_compare_keys( const DEVPROPKEY *keys, ULONG keys_len, const DEVPROPCOMPKEY **comp_keys, ULONG *comp_keys_len )
{
if (!(*comp_keys_len = keys_len)) return ERROR_SUCCESS;
if (!(*comp_keys = calloc( keys_len, sizeof(**comp_keys) ))) return ERROR_OUTOFMEMORY;
for (UINT i = 0; i < keys_len; ++i) ((DEVPROPCOMPKEY *)*comp_keys)[i].Key = keys[i];
return ERROR_SUCCESS;
}
static LSTATUS copy_device_interface_property_keys( HKEY hkey, const struct device_interface *iface, const DEVPROPCOMPKEY **comp_keys, ULONG *comp_keys_len )
{
DEVPROPKEY *tmp, *keys = NULL;
LSTATUS err;
for (;;)
{
if (!(err = enum_device_interface_property_keys( hkey, iface, keys, comp_keys_len ))) break;
if (err != ERROR_MORE_DATA) return err;
if (!(tmp = realloc( keys, *comp_keys_len * sizeof(*keys) ))) return ERROR_OUTOFMEMORY;
keys = tmp;
}
get_property_compare_keys( keys, *comp_keys_len, comp_keys, comp_keys_len );
free( keys );
return ERROR_SUCCESS;
}
typedef LSTATUS (*enum_device_object_cb)( DEV_OBJECT_TYPE type, const WCHAR *id, ULONG *props_len, DEVPROPERTY **props, void *context );
static UINT select_property( const DEVPROPCOMPKEY *key, DEVPROPERTY *props, DEVPROPERTY *select_end, DEVPROPERTY *props_end )
{
for (DEVPROPERTY *prop = select_end; prop < props_end; prop++)
{
if (IsEqualDevPropCompKey( prop->CompKey, *key ))
{
DEVPROPERTY tmp = *select_end;
*select_end = *prop;
*prop = tmp;
return 1;
}
}
return 0;
}
static void select_properties( const DEVPROPCOMPKEY *keys, ULONG keys_len, DEVPROPERTY **props, ULONG *props_len )
{
DEVPROPERTY *select_end = *props, *props_end = *props + *props_len;
for (UINT i = 0; i < keys_len; i++) select_end += select_property( keys + i, *props, select_end, props_end );
*props_len = select_end - *props;
while (select_end < props_end) free( (select_end++)->Buffer );
if (!*props_len)
{
free( *props );
*props = NULL;
}
}
struct enum_dev_object_params
{
DEV_OBJECT_TYPE type;
const DEVPROPCOMPKEY *props;
ULONG props_len;
BOOL all_props;
const DEVPROP_FILTER_EXPRESSION *filters;
const DEVPROP_FILTER_EXPRESSION *filters_end;
enum_device_object_cb callback;
void *context;
};
static LSTATUS enum_dev_objects_device_interface( HKEY hkey, const void *object, const WCHAR *name, UINT name_len, void *context )
{
struct enum_dev_object_params *params = context;
ULONG keys_len = params->props_len, properties_len = 0;
const struct device_interface *iface = object;
const DEVPROPCOMPKEY *keys = params->props;
DEVPROPERTY *properties = NULL;
LSTATUS err = ERROR_SUCCESS;
BOOL matches;
TRACE( "hkey %p object %s name %s\n", hkey, debugstr_device_interface(iface), debugstr_w(name) );
if (params->all_props || params->filters) err = copy_device_interface_property_keys( hkey, iface, &keys, &keys_len );
if (!err)
{
if (keys_len && !(properties = calloc( keys_len, sizeof(*properties) ))) err = ERROR_OUTOFMEMORY;
else err = copy_device_interface_properties( hkey, iface, keys, keys_len, properties, &properties_len );
}
if (!err)
{
if (params->filters) qsort( properties, properties_len, sizeof(*properties), devproperty_compare );
matches = devprop_filter_matches_properties( properties, properties_len, DEVPROP_OPERATOR_AND_OPEN, params->filters, params->filters_end );
if (!params->all_props) select_properties( params->props, params->props_len, &properties, &properties_len );
if (matches) err = params->callback( params->type, name, &properties_len, &properties, params->context );
}
DevFreeObjectProperties( properties_len, properties );
if (params->all_props || params->filters) free( (void *)keys );
return err;
}
static LSTATUS enum_dev_objects( DEV_OBJECT_TYPE type, const DEVPROPCOMPKEY *props, ULONG props_len, BOOL all_props,
const DEVPROP_FILTER_EXPRESSION *filters, const DEVPROP_FILTER_EXPRESSION *filters_end,
enum_device_object_cb callback, void *context )
{
struct enum_dev_object_params params =
{
.type = type,
.props = props,
.props_len = props_len,
.all_props = all_props,
.filters = filters,
.filters_end = filters_end,
.callback = callback,
.context = context,
};
switch (type)
{
case DevObjectTypeDeviceInterface:
case DevObjectTypeDeviceInterfaceDisplay:
return enum_device_interfaces( TRUE, enum_dev_objects_device_interface, ¶ms );
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 ERROR_SUCCESS;
}
}
struct objects_list
{
DEV_OBJECT *objects;
ULONG len;
};
static LSTATUS dev_objects_append( DEV_OBJECT_TYPE type, const WCHAR *id, ULONG *props_len, DEVPROPERTY **props, void *context )
{
struct objects_list *list = context;
DEV_OBJECT *tmp, *obj;
if (!(tmp = realloc( list->objects, (list->len + 1) * sizeof(*list->objects) ))) return ERROR_OUTOFMEMORY;
list->objects = tmp;
obj = list->objects + list->len;
if (!(obj->pszObjectId = wcsdup( id ))) return ERROR_OUTOFMEMORY;
obj->ObjectType = type;
obj->cPropertyCount = *props_len;
obj->pProperties = *props;
*props_len = 0;
*props = NULL;
list->len++;
return ERROR_SUCCESS;
}
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};
LSTATUS err;
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))
|| (filters && !devprop_filters_validate( filters, filters + filters_len )))
return E_INVALIDARG;
if (params)
FIXME( "Query parameters are not supported!\n" );
*objs = NULL;
*objs_len = 0;
if ((err = enum_dev_objects( type, props, props_len, !!(flags & DevQueryFlagAllProperties), filters, filters + filters_len,
dev_objects_append, &objects )))
{
DevFreeObjects( objects.len, objects.objects );
return HRESULT_FROM_WIN32(err);
}
*objs = objects.objects;
*objs_len = objects.len;
return S_OK;
}
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 LSTATUS device_query_context_add_object( DEV_OBJECT_TYPE type, const WCHAR *id, ULONG *props_len, DEVPROPERTY **props, void *data )
{
DEV_QUERY_RESULT_ACTION_DATA action_data =
{
.Action = DevQueryResultAdd,
.Data.DeviceObject =
{
.ObjectType = type,
.pszObjectId = id,
.cPropertyCount = *props_len,
.pProperties = *props,
},
};
struct device_query_context *ctx = data;
struct device_iface_path *iface_entry = NULL;
LSTATUS err = ERROR_SUCCESS;
TRACE( "(%s, %p)\n", debugstr_w( id ), data );
ctx->callback( (HDEVQUERY)ctx, ctx->user_data, &action_data );
EnterCriticalSection( &ctx->cs );
if (ctx->state == DevQueryStateClosed)
err = ERROR_CANCELLED;
else if (type == DevObjectTypeDeviceInterface || type == DevObjectTypeDeviceInterfaceDisplay)
{
if (!(iface_entry = calloc( 1, sizeof( *iface_entry ) )) || !(iface_entry->path = wcsdup( id )))
{
if (iface_entry) free( iface_entry->path );
free( iface_entry );
err = ERROR_OUTOFMEMORY;
}
else if (rb_put( &ctx->known_ifaces, iface_entry->path, &iface_entry->entry ))
{
free( iface_entry->path );
free( iface_entry );
}
}
LeaveCriticalSection( &ctx->cs );
return err;
}
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;
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;
LSTATUS err = ERROR_SUCCESS;
BOOL success;
if (!ctx->filters)
err = enum_dev_objects( ctx->type, ctx->prop_keys, ctx->prop_keys_len, !!(ctx->flags & DevQueryFlagAllProperties),
0, NULL, device_query_context_add_object, ctx );
EnterCriticalSection( &ctx->cs );
if (ctx->state == DevQueryStateClosed)
err = ERROR_CANCELLED;
switch (err)
{
case ERROR_SUCCESS:
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 ERROR_CANCELLED:
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))
|| (filters && !devprop_filters_validate( filters, filters + filters_len )))
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 );
}
if (async && old == DevQueryStateEnumCompleted)
{
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 );
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 )
{
ULONG valid_flags = DevQueryFlagAllProperties | DevQueryFlagLocalize;
BOOL all_props = flags & DevQueryFlagAllProperties;
LSTATUS err = ERROR_SUCCESS;
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:
{
ULONG properties_len = 0, keys_len = props_len;
const DEVPROPCOMPKEY *keys = props;
DEVPROPERTY *properties = NULL;
struct device_interface iface;
HKEY hkey;
if ((err = init_device_interface( &iface, id ))) break;
if ((err = open_device_interface_key( &iface, KEY_ALL_ACCESS, TRUE, &hkey ))) break;
if (all_props) err = copy_device_interface_property_keys( hkey, &iface, &keys, &keys_len );
if (!err)
{
if ((properties_len = keys_len) && !(properties = calloc( keys_len, sizeof(*properties) ))) err = ERROR_OUTOFMEMORY;
else err = copy_device_interface_properties( hkey, &iface, keys, keys_len, properties, &properties_len );
}
*buf = properties;
*buf_len = properties_len;
if (keys != props) free( (void *)keys );
RegCloseKey( hkey );
break;
}
default:
FIXME( "Unsupported DEV_OBJECT_TYPE: %d\n", type );
err = ERROR_FILE_NOT_FOUND;
break;
}
if (err) return HRESULT_FROM_WIN32(err);
return S_OK;
}
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 );
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 );
}