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
@@ -24,6 +31,77 @@ const (
2431 _DEVPKEY_LOCATIONPATHS_GUID = "a45c254e-df1c-4efd-8020-67d146a850e0"
2532)
2633
34+ var (
35+ // class GUID for devices with interface type Disk
36+ // 53f56307-b6bf-11d0-94f2-00a0c91efb8b
37+ devClassDiskGUID = guid.GUID {
38+ Data1 : 0x53f56307 ,
39+ Data2 : 0xb6bf ,
40+ Data3 : 0x11d0 ,
41+ Data4 : [8 ]byte {0x94 , 0xf2 , 0x00 , 0xa0 , 0xc9 , 0x1e , 0xfb , 0x8b },
42+ }
43+ )
44+
45+ // SCSI_ADDRESS structure used with IOCTL_SCSI_GET_ADDRESS.
46+ // defined here: https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddscsi/ns-ntddscsi-_scsi_address
47+ type SCSIAddress struct {
48+ Length uint32
49+ PortNumber uint8
50+ PathId uint8
51+ TargetId uint8
52+ Lun uint8
53+ }
54+
55+ // STORAGE_DEVICE_NUMBER structure used with IOCTL_STORAGE_GET_DEVICE_NUMBER
56+ // https://learn.microsoft.com/en-us/windows/win32/api/winioctl/ns-winioctl-storage_device_number
57+ type StorageDeviceNumber struct {
58+ DeviceType uint32
59+ DeviceNumber uint32
60+ PartitionNumber uint32
61+ }
62+
63+ // getScsiAddress retrieves the SCSI address from a given disk handle
64+ func getScsiAddress (ctx context.Context , handle windows.Handle ) (* SCSIAddress , error ) {
65+ // Create a SCSI_ADDRESS structure to receive the address information
66+ address := & SCSIAddress {}
67+ address .Length = uint32 (unsafe .Sizeof (SCSIAddress {}))
68+
69+ // Buffer for the returned data
70+ var bytesReturned uint32
71+
72+ // Call DeviceIoControl with IOCTL_SCSI_GET_ADDRESS
73+ err := windows .DeviceIoControl (
74+ handle ,
75+ _IOCTL_SCSI_GET_ADDRESS ,
76+ nil , 0 , // no input buffer
77+ (* byte )(unsafe .Pointer (address )),
78+ address .Length , & bytesReturned , nil )
79+ if err != nil {
80+ return nil , fmt .Errorf ("DeviceIoControl failed with error: %w" , err )
81+ }
82+ if bytesReturned <= 0 {
83+ return nil , fmt .Errorf ("DeviceIoControl returned %d bytes" , bytesReturned )
84+ }
85+ return address , nil
86+ }
87+
88+ func getStorageDeviceNumber (ctx context.Context , handle windows.Handle ) (* StorageDeviceNumber , error ) {
89+ var bytesReturned uint32
90+ var deviceNumber StorageDeviceNumber
91+ if err := windows .DeviceIoControl (
92+ handle ,
93+ _IOCTL_STORAGE_GET_DEVICE_NUMBER ,
94+ nil , 0 , // No input buffer
95+ (* byte )(unsafe .Pointer (& deviceNumber )),
96+ uint32 (unsafe .Sizeof (deviceNumber )),
97+ & bytesReturned ,
98+ nil ,
99+ ); err != nil {
100+ return nil , fmt .Errorf ("get device number ioctl failed: %w" , err )
101+ }
102+ return & deviceNumber , nil
103+ }
104+
27105// getDevPKeyDeviceLocationPaths creates a DEVPROPKEY struct for the
28106// DEVPKEY_Device_LocationPaths property as defined in devpkey.h
29107func getDevPKeyDeviceLocationPaths () (* winapi.DevPropKey , error ) {
@@ -121,3 +199,92 @@ func getDeviceIDList(pszFilter *byte, ulFlags uint32) ([]string, error) {
121199
122200 return winapi .ConvertStringSetToSlice (buf )
123201}
202+
203+ // A device interface class represents a conceptual functionality that any device in that
204+ // class should support or represent such as a particular I/O contract. There are several
205+ // predefined interface classes and each class is identified by its own unique GUID. A
206+ // single device can implement/support multiple interface classes and there can be
207+ // multiple devices in the system that implement/support a particular interface class. A
208+ // device that implements a particular interface class is referred to as a device
209+ // interface instance. (For further details see:
210+ // https://learn.microsoft.com/en-us/windows-hardware/drivers/install/overview-of-device-interface-classes).
211+ //
212+ // getDeviceInterfaceInstancesByClass retrieves a list of device interface instances for
213+ // all the devices that are currently attached to the system filtered by the given
214+ // interface class(`interfaceClassGUID`). By default this only returns the list of devices
215+ // that are currently attached to the system. If `includeNonAttached` is true, includes
216+ // the devices that the system has seen earlier but aren't currently attached.
217+ //
218+ // For further details see: https://learn.microsoft.com/en-us/windows/win32/api/cfgmgr32/nf-cfgmgr32-cm_get_device_interface_lista
219+ func getDeviceInterfaceInstancesByClass (ctx context.Context , interfaceClassGUID * guid.GUID , includeNonAttached bool ) (_ []string , err error ) {
220+ log .G (ctx ).WithFields (logrus.Fields {
221+ "inteface class" : interfaceClassGUID ,
222+ "includeNonAttached" : includeNonAttached ,
223+ }).Debugf ("get device interface instances by class" )
224+
225+ interfaceListSize := uint32 (0 )
226+ ulFlags := _CM_GET_DEVICE_INTERFACE_LIST_PRESENT
227+ if includeNonAttached {
228+ ulFlags = _CM_GET_DEVICE_INTERFACE_LIST_ALL_DEVICES
229+ }
230+
231+ if err := winapi .CMGetDeviceInterfaceListSize (& interfaceListSize , interfaceClassGUID , nil , ulFlags ); err != nil {
232+ return nil , fmt .Errorf ("failed to get size of device interface list: %w" , err )
233+ }
234+
235+ log .G (ctx ).WithField ("interface list size" , interfaceListSize ).Trace ("retrieved device interface list size" )
236+
237+ buf := make ([]uint16 , interfaceListSize )
238+ if err := winapi .CMGetDeviceInterfaceList (& devClassDiskGUID , nil , & buf [0 ], interfaceListSize , 0 ); err != nil {
239+ return nil , fmt .Errorf ("failed to get device interface list: %w" , err )
240+ }
241+ return convertNullSeparatedUint16BufToStringSlice (buf ), nil
242+ }
243+
244+ // GetDevicePathAndNumberFromControllerLUN finds the storage device that has a matching
245+ // `controller` and `LUN` and returns a physical device number of that device. This device
246+ // number can then be used to make a path of that device and open handles to that device,
247+ // mount that disk etc.
248+ func GetDevicePathAndNumberFromControllerLUN (ctx context.Context , controller , LUN uint8 ) (string , uint32 , error ) {
249+ interfacePaths , err := getDeviceInterfaceInstancesByClass (ctx , & devClassDiskGUID , false )
250+ if err != nil {
251+ return "" , 0 , fmt .Errorf ("failed to get device interface instances: %w" , err )
252+ }
253+
254+ log .G (ctx ).Debugf ("disk device interface list: %+v" , interfacePaths )
255+
256+ // go over each disk device interface and find out its LUN
257+ for _ , iPath := range interfacePaths {
258+ utf16Path , err := windows .UTF16PtrFromString (iPath )
259+ if err != nil {
260+ return "" , 0 , fmt .Errorf ("failed to convert interface path [%s] to utf16: %w" , iPath , err )
261+ }
262+
263+ handle , err := windows .CreateFile (utf16Path , windows .GENERIC_READ | windows .GENERIC_WRITE ,
264+ windows .FILE_SHARE_READ | windows .FILE_SHARE_WRITE ,
265+ nil , windows .OPEN_EXISTING , 0 , 0 )
266+ if err != nil {
267+ return "" , 0 , fmt .Errorf ("failed to get handle to interface path [%s]: %w" , iPath , err )
268+ }
269+ defer windows .Close (handle )
270+
271+ scsiAddr , err := getScsiAddress (ctx , handle )
272+ if err != nil {
273+ return "" , 0 , fmt .Errorf ("failed to get SCSI address for interface path [%s]: %w" , iPath , err )
274+ }
275+ log .G (ctx ).WithFields (logrus.Fields {
276+ "device interface path" : iPath ,
277+ "scsi address" : scsiAddr ,
278+ }).Trace ("scsi path from device interface path" )
279+
280+ //TODO(ambarve): is comparing controller with port number the correct way?
281+ if scsiAddr .Lun == LUN && scsiAddr .PortNumber == controller {
282+ deviceNumber , err := getStorageDeviceNumber (ctx , handle )
283+ if err != nil {
284+ return "" , 0 , fmt .Errorf ("failed to get physical device number: %w" , err )
285+ }
286+ return iPath , deviceNumber .DeviceNumber , nil
287+ }
288+ }
289+ return "" , 0 , fmt .Errorf ("no device found with controller: %d & LUN:%d" , controller , LUN )
290+ }
0 commit comments