Skip to content

Commit 0011e2e

Browse files
ambarvekiashok
authored andcommitted
Support for querying disks based on LUN
Adds the Go wrappers on windows device query APIs to allow finding a disk device by the provided LUN. This is mostly required inside the GCS where shim includes the LUN at which a disk is attaches in the request and then the GCS needs to be able to find that disk to use it further. Signed-off-by: Amit Barve <[email protected]> (cherry picked from commit ab0338a) Signed-off-by: Kirtana Ashok <[email protected]>
1 parent 92b7881 commit 0011e2e

File tree

2 files changed

+231
-1
lines changed

2 files changed

+231
-1
lines changed

internal/windevice/devicequery.go

Lines changed: 168 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,24 @@
33
package windevice
44

55
import (
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

1520
const (
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
29107
func 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+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
//go:build windows
2+
3+
package windevice
4+
5+
import (
6+
"context"
7+
"path/filepath"
8+
"testing"
9+
"time"
10+
11+
"github.com/Microsoft/go-winio/vhd"
12+
)
13+
14+
func TestDeviceInterfaceInstancesWithPnpUtil(t *testing.T) {
15+
ctx := context.Background()
16+
initialInterfacesList, err := getDeviceInterfaceInstancesByClass(ctx, &devClassDiskGUID, false)
17+
if err != nil {
18+
t.Fatalf("failed to get initial disk interfaces: %v", err)
19+
}
20+
t.Logf("initial interface list: %+v\n", initialInterfacesList)
21+
22+
// make a fake VHD and attach it.
23+
tempDir := t.TempDir()
24+
vhdxPath := filepath.Join(tempDir, "test.vhdx")
25+
if err := vhd.CreateVhdx(vhdxPath, 1, 1); err != nil {
26+
t.Fatalf("failed to create vhd: %s", err)
27+
}
28+
if err := vhd.AttachVhd(vhdxPath); err != nil {
29+
t.Fatalf("failed to attach vhd: %s", err)
30+
}
31+
t.Cleanup(func() {
32+
if err := vhd.DetachVhd(vhdxPath); err != nil {
33+
t.Logf("failed to detach VHD during cleanup: %s\n", err)
34+
}
35+
})
36+
37+
interfaceListAfterVHDAttach, err := getDeviceInterfaceInstancesByClass(ctx, &devClassDiskGUID, false)
38+
if err != nil {
39+
t.Fatalf("failed to get initial disk interfaces: %v", err)
40+
}
41+
t.Logf("interface list after attaching VHD: %+v\n", interfaceListAfterVHDAttach)
42+
43+
if len(initialInterfacesList) != (len(interfaceListAfterVHDAttach) - 1) {
44+
t.Fatalf("expected to find exactly 1 new interface in the returned interfaces list")
45+
}
46+
47+
if err := vhd.DetachVhd(vhdxPath); err != nil {
48+
t.Fatalf("failed to detach VHD: %s\n", err)
49+
}
50+
51+
// Looks like a small time gap is required before we query for device interfaces again to get the updated list.
52+
time.Sleep(2 * time.Second)
53+
54+
finalInterfaceList, err := getDeviceInterfaceInstancesByClass(ctx, &devClassDiskGUID, false)
55+
if err != nil {
56+
t.Fatalf("failed to get initial disk interfaces: %v", err)
57+
}
58+
t.Logf("interface list after detaching VHD: %+v\n", finalInterfaceList)
59+
60+
if len(initialInterfacesList) != len(finalInterfaceList) {
61+
t.Fatalf("expected interface lists to have same length")
62+
}
63+
}

0 commit comments

Comments
 (0)