1
2
3 package windevice
4
5 import (
6 "fmt"
7 "strings"
8 "unicode/utf16"
9
10 "github.com/Microsoft/go-winio/pkg/guid"
11 "github.com/Microsoft/hcsshim/internal/winapi"
12 "github.com/pkg/errors"
13 )
14
15 const (
16 _CM_GETIDLIST_FILTER_BUSRELATIONS uint32 = 0x00000020
17
18 _CM_LOCATE_DEVNODE_NORMAL uint32 = 0x00000000
19
20 _DEVPROP_TYPE_STRING uint32 = 0x00000012
21 _DEVPROP_TYPEMOD_LIST uint32 = 0x00002000
22 _DEVPROP_TYPE_STRING_LIST uint32 = (_DEVPROP_TYPE_STRING | _DEVPROP_TYPEMOD_LIST)
23
24 _DEVPKEY_LOCATIONPATHS_GUID = "a45c254e-df1c-4efd-8020-67d146a850e0"
25 )
26
27
28
29 func getDevPKeyDeviceLocationPaths() (*winapi.DevPropKey, error) {
30 guid, err := guid.FromString(_DEVPKEY_LOCATIONPATHS_GUID)
31 if err != nil {
32 return nil, err
33 }
34 return &winapi.DevPropKey{
35 Fmtid: guid,
36
37 Pid: 37,
38 }, nil
39 }
40
41 func GetDeviceLocationPathsFromIDs(ids []string) ([]string, error) {
42 result := []string{}
43 devPKeyDeviceLocationPaths, err := getDevPKeyDeviceLocationPaths()
44 if err != nil {
45 return nil, err
46 }
47 for _, id := range ids {
48 var devNodeInst uint32
49 err = winapi.CMLocateDevNode(&devNodeInst, id, _CM_LOCATE_DEVNODE_NORMAL)
50 if err != nil {
51 return nil, errors.Wrapf(err, "failed to locate device node for %s", id)
52 }
53 propertyType := uint32(0)
54 propertyBufferSize := uint32(0)
55
56
57 err = winapi.CMGetDevNodeProperty(devNodeInst, devPKeyDeviceLocationPaths, &propertyType, nil, &propertyBufferSize, 0)
58 if err != nil {
59 return nil, errors.Wrapf(err, "failed to get property buffer size of devnode query for %s with", id)
60 }
61
62
63 propertyBuffer := make([]uint16, propertyBufferSize/2)
64 err = winapi.CMGetDevNodeProperty(devNodeInst, devPKeyDeviceLocationPaths, &propertyType, &propertyBuffer[0], &propertyBufferSize, 0)
65 if err != nil {
66 return nil, errors.Wrapf(err, "failed to get location path property from device node for %s with", id)
67 }
68 if propertyType != _DEVPROP_TYPE_STRING_LIST {
69 return nil, fmt.Errorf("expected to return property type DEVPROP_TYPE_STRING_LIST %d, instead got %d", _DEVPROP_TYPE_STRING_LIST, propertyType)
70 }
71 if int(propertyBufferSize/2) > len(propertyBuffer) {
72 return nil, fmt.Errorf("location path is too large for the buffer, size in bytes %d", propertyBufferSize)
73 }
74
75 formattedResult, err := convertFirstNullTerminatedValueToString(propertyBuffer[:propertyBufferSize/2])
76 if err != nil {
77 return nil, err
78 }
79 result = append(result, formattedResult)
80 }
81
82 return result, nil
83 }
84
85
86 func convertFirstNullTerminatedValueToString(buf []uint16) (string, error) {
87 r := utf16.Decode(buf)
88 converted := string(r)
89 zerosIndex := strings.IndexRune(converted, '\u0000')
90 if zerosIndex == -1 {
91 return "", errors.New("cannot convert value to string, malformed data passed")
92 }
93 return converted[:zerosIndex], nil
94 }
95
96 func GetChildrenFromInstanceIDs(parentIDs []string) ([]string, error) {
97 var result []string
98 for _, id := range parentIDs {
99 pszFilterParentID := []byte(id)
100 children, err := getDeviceIDList(&pszFilterParentID[0], _CM_GETIDLIST_FILTER_BUSRELATIONS)
101 if err != nil {
102 return nil, err
103 }
104 result = append(result, children...)
105 }
106 return result, nil
107 }
108
109 func getDeviceIDList(pszFilter *byte, ulFlags uint32) ([]string, error) {
110 listLength := uint32(0)
111 if err := winapi.CMGetDeviceIDListSize(&listLength, pszFilter, ulFlags); err != nil {
112 return nil, err
113 }
114 if listLength == 0 {
115 return []string{}, nil
116 }
117 buf := make([]byte, listLength)
118 if err := winapi.CMGetDeviceIDList(pszFilter, &buf[0], uint32(listLength), ulFlags); err != nil {
119 return nil, err
120 }
121
122 return winapi.ConvertStringSetToSlice(buf)
123 }
124
View as plain text