33package windevice
44
55import (
6+ "context"
67 "fmt"
78 "strings"
89 "unicode/utf16"
10+ "unsafe"
911
1012 "github.com/Microsoft/go-winio/pkg/guid"
13+ "github.com/Microsoft/hcsshim/internal/log"
1114 "github.com/Microsoft/hcsshim/internal/winapi"
1215 "github.com/pkg/errors"
16+ "github.com/sirupsen/logrus"
17+ "golang.org/x/sys/windows"
1318)
1419
1520const (
16- _CM_GETIDLIST_FILTER_BUSRELATIONS uint32 = 0x00000020
21+ _CM_GETIDLIST_FILTER_BUSRELATIONS uint32 = 0x00000020
22+ _CM_GET_DEVICE_INTERFACE_LIST_PRESENT uint32 = 0x00000000
23+ _CM_GET_DEVICE_INTERFACE_LIST_ALL_DEVICES uint32 = 0x00000001
1724
1825 _CM_LOCATE_DEVNODE_NORMAL uint32 = 0x00000000
1926
@@ -22,8 +29,82 @@ const (
2229 _DEVPROP_TYPE_STRING_LIST uint32 = (_DEVPROP_TYPE_STRING | _DEVPROP_TYPEMOD_LIST )
2330
2431 _DEVPKEY_LOCATIONPATHS_GUID = "a45c254e-df1c-4efd-8020-67d146a850e0"
32+
33+ _IOCTL_SCSI_GET_ADDRESS = 0x41018
34+ _IOCTL_STORAGE_GET_DEVICE_NUMBER = 0x2d1080
35+ )
36+
37+ var (
38+ // class GUID for devices with interface type Disk
39+ // 53f56307-b6bf-11d0-94f2-00a0c91efb8b
40+ devClassDiskGUID = guid.GUID {
41+ Data1 : 0x53f56307 ,
42+ Data2 : 0xb6bf ,
43+ Data3 : 0x11d0 ,
44+ Data4 : [8 ]byte {0x94 , 0xf2 , 0x00 , 0xa0 , 0xc9 , 0x1e , 0xfb , 0x8b },
45+ }
2546)
2647
48+ // SCSI_ADDRESS structure used with IOCTL_SCSI_GET_ADDRESS.
49+ // defined here: https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddscsi/ns-ntddscsi-_scsi_address
50+ type SCSIAddress struct {
51+ Length uint32
52+ PortNumber uint8
53+ PathId uint8
54+ TargetId uint8
55+ Lun uint8
56+ }
57+
58+ // STORAGE_DEVICE_NUMBER structure used with IOCTL_STORAGE_GET_DEVICE_NUMBER
59+ // https://learn.microsoft.com/en-us/windows/win32/api/winioctl/ns-winioctl-storage_device_number
60+ type StorageDeviceNumber struct {
61+ DeviceType uint32
62+ DeviceNumber uint32
63+ PartitionNumber uint32
64+ }
65+
66+ // getScsiAddress retrieves the SCSI address from a given disk handle
67+ func getScsiAddress (ctx context.Context , handle windows.Handle ) (* SCSIAddress , error ) {
68+ // Create a SCSI_ADDRESS structure to receive the address information
69+ address := & SCSIAddress {}
70+ address .Length = uint32 (unsafe .Sizeof (SCSIAddress {}))
71+
72+ // Buffer for the returned data
73+ var bytesReturned uint32
74+
75+ // Call DeviceIoControl with IOCTL_SCSI_GET_ADDRESS
76+ err := windows .DeviceIoControl (
77+ handle ,
78+ _IOCTL_SCSI_GET_ADDRESS ,
79+ nil , 0 , // no input buffer
80+ (* byte )(unsafe .Pointer (address )),
81+ address .Length , & bytesReturned , nil )
82+ if err != nil {
83+ return nil , fmt .Errorf ("DeviceIoControl failed with error: %w" , err )
84+ }
85+ if bytesReturned <= 0 {
86+ return nil , fmt .Errorf ("DeviceIoControl returned %d bytes" , bytesReturned )
87+ }
88+ return address , nil
89+ }
90+
91+ func getStorageDeviceNumber (ctx context.Context , handle windows.Handle ) (* StorageDeviceNumber , error ) {
92+ var bytesReturned uint32
93+ var deviceNumber StorageDeviceNumber
94+ if err := windows .DeviceIoControl (
95+ handle ,
96+ _IOCTL_STORAGE_GET_DEVICE_NUMBER ,
97+ nil , 0 , // No input buffer
98+ (* byte )(unsafe .Pointer (& deviceNumber )),
99+ uint32 (unsafe .Sizeof (deviceNumber )),
100+ & bytesReturned ,
101+ nil ,
102+ ); err != nil {
103+ return nil , fmt .Errorf ("get device number ioctl failed: %w" , err )
104+ }
105+ return & deviceNumber , nil
106+ }
107+
27108// getDevPKeyDeviceLocationPaths creates a DEVPROPKEY struct for the
28109// DEVPKEY_Device_LocationPaths property as defined in devpkey.h
29110func getDevPKeyDeviceLocationPaths () (* winapi.DevPropKey , error ) {
@@ -93,6 +174,21 @@ func convertFirstNullTerminatedValueToString(buf []uint16) (string, error) {
93174 return converted [:zerosIndex ], nil
94175}
95176
177+ func convertNullSeparatedUint16BufToStringSlice (buf []uint16 ) []string {
178+ result := []string {}
179+ r := utf16 .Decode (buf )
180+ converted := string (r )
181+ for {
182+ i := strings .IndexRune (converted , '\u0000' )
183+ if i <= 0 {
184+ break
185+ }
186+ result = append (result , string (converted [:i ]))
187+ converted = converted [i + 1 :]
188+ }
189+ return result
190+ }
191+
96192func GetChildrenFromInstanceIDs (parentIDs []string ) ([]string , error ) {
97193 var result []string
98194 for _ , id := range parentIDs {
@@ -121,3 +217,92 @@ func getDeviceIDList(pszFilter *byte, ulFlags uint32) ([]string, error) {
121217
122218 return winapi .ConvertStringSetToSlice (buf )
123219}
220+
221+ // A device interface class represents a conceptual functionality that any device in that
222+ // class should support or represent such as a particular I/O contract. There are several
223+ // predefined interface classes and each class is identified by its own unique GUID. A
224+ // single device can implement/support multiple interface classes and there can be
225+ // multiple devices in the system that implement/support a particular interface class. A
226+ // device that implements a particular interface class is referred to as a device
227+ // interface instance. (For further details see:
228+ // https://learn.microsoft.com/en-us/windows-hardware/drivers/install/overview-of-device-interface-classes).
229+ //
230+ // getDeviceInterfaceInstancesByClass retrieves a list of device interface instances for
231+ // all the devices that are currently attached to the system filtered by the given
232+ // interface class(`interfaceClassGUID`). By default this only returns the list of devices
233+ // that are currently attached to the system. If `includeNonAttached` is true, includes
234+ // the devices that the system has seen earlier but aren't currently attached.
235+ //
236+ // For further details see: https://learn.microsoft.com/en-us/windows/win32/api/cfgmgr32/nf-cfgmgr32-cm_get_device_interface_lista
237+ func getDeviceInterfaceInstancesByClass (ctx context.Context , interfaceClassGUID * guid.GUID , includeNonAttached bool ) (_ []string , err error ) {
238+ log .G (ctx ).WithFields (logrus.Fields {
239+ "inteface class" : interfaceClassGUID ,
240+ "includeNonAttached" : includeNonAttached ,
241+ }).Debugf ("get device interface instances by class" )
242+
243+ interfaceListSize := uint32 (0 )
244+ ulFlags := _CM_GET_DEVICE_INTERFACE_LIST_PRESENT
245+ if includeNonAttached {
246+ ulFlags = _CM_GET_DEVICE_INTERFACE_LIST_ALL_DEVICES
247+ }
248+
249+ if err := winapi .CMGetDeviceInterfaceListSize (& interfaceListSize , interfaceClassGUID , nil , ulFlags ); err != nil {
250+ return nil , fmt .Errorf ("failed to get size of device interface list: %w" , err )
251+ }
252+
253+ log .G (ctx ).WithField ("interface list size" , interfaceListSize ).Trace ("retrieved device interface list size" )
254+
255+ buf := make ([]uint16 , interfaceListSize )
256+ if err := winapi .CMGetDeviceInterfaceList (& devClassDiskGUID , nil , & buf [0 ], interfaceListSize , 0 ); err != nil {
257+ return nil , fmt .Errorf ("failed to get device interface list: %w" , err )
258+ }
259+ return convertNullSeparatedUint16BufToStringSlice (buf ), nil
260+ }
261+
262+ // GetDeviceNumberFromControllerLUN finds the storage device that has a matching
263+ // `controller` and `LUN` and returns a physical device number of that device. This device
264+ // number can then be used to make a path of that device and open handles to that device,
265+ // mount that disk etc.
266+ func GetDeviceNumberFromControllerLUN (ctx context.Context , controller , LUN uint8 ) (uint32 , error ) {
267+ interfacePaths , err := getDeviceInterfaceInstancesByClass (ctx , & devClassDiskGUID , false )
268+ if err != nil {
269+ return 0 , fmt .Errorf ("failed to get device interface instances: %w" , err )
270+ }
271+
272+ log .G (ctx ).Debugf ("disk device interface list: %+v" , interfacePaths )
273+
274+ // go over each disk device interface and find out its LUN
275+ for _ , iPath := range interfacePaths {
276+ utf16Path , err := windows .UTF16PtrFromString (iPath )
277+ if err != nil {
278+ return 0 , fmt .Errorf ("failed to convert interface path [%s] to utf16: %w" , iPath , err )
279+ }
280+
281+ handle , err := windows .CreateFile (utf16Path , windows .GENERIC_READ | windows .GENERIC_WRITE ,
282+ windows .FILE_SHARE_READ | windows .FILE_SHARE_WRITE ,
283+ nil , windows .OPEN_EXISTING , 0 , 0 )
284+ if err != nil {
285+ return 0 , fmt .Errorf ("failed to get handle to interface path [%s]: %w" , iPath , err )
286+ }
287+ defer windows .Close (handle )
288+
289+ scsiAddr , err := getScsiAddress (ctx , handle )
290+ if err != nil {
291+ return 0 , fmt .Errorf ("failed to get SCSI address for interface path [%s]: %w" , iPath , err )
292+ }
293+ log .G (ctx ).WithFields (logrus.Fields {
294+ "device interface path" : iPath ,
295+ "scsi address" : scsiAddr ,
296+ }).Trace ("scsi path from device interface path" )
297+
298+ //TODO(ambarve): is comparing controller with port number the correct way?
299+ if scsiAddr .Lun == LUN && scsiAddr .PortNumber == controller {
300+ deviceNumber , err := getStorageDeviceNumber (ctx , handle )
301+ if err != nil {
302+ return 0 , fmt .Errorf ("failed to get physical device number: %w" , err )
303+ }
304+ return deviceNumber .DeviceNumber , nil
305+ }
306+ }
307+ return 0 , fmt .Errorf ("no device found with controller: %d & LUN:%d" , controller , LUN )
308+ }
0 commit comments