44package vhd
55
66import (
7+ "bytes"
8+ "encoding/binary"
79 "fmt"
10+ "strings"
811 "syscall"
12+ "unsafe"
913
1014 "github.com/Microsoft/go-winio/pkg/guid"
1115 "golang.org/x/sys/windows"
@@ -18,6 +22,8 @@ import (
1822//sys attachVirtualDisk(handle syscall.Handle, securityDescriptor *uintptr, attachVirtualDiskFlag uint32, providerSpecificFlags uint32, parameters *AttachVirtualDiskParameters, overlapped *syscall.Overlapped) (win32err error) = virtdisk.AttachVirtualDisk
1923//sys detachVirtualDisk(handle syscall.Handle, detachVirtualDiskFlags uint32, providerSpecificFlags uint32) (win32err error) = virtdisk.DetachVirtualDisk
2024//sys getVirtualDiskPhysicalPath(handle syscall.Handle, diskPathSizeInBytes *uint32, buffer *uint16) (win32err error) = virtdisk.GetVirtualDiskPhysicalPath
25+ //sys getVirtualDiskInformation(handle syscall.Handle, bufferSize *uint32, info *virtualDiskInfo, sizeUsed *uint32) (win32err error) = virtdisk.GetVirtualDiskInformation
26+ //sys setVirtualDiskInformation(handle syscall.Handle, info *virtualDiskInfo) (win32err error) = virtdisk.SetVirtualDiskInformation
2127
2228type (
2329 CreateVirtualDiskFlag uint32
@@ -86,6 +92,20 @@ type AttachVirtualDiskParameters struct {
8692 Version2 AttachVersion2
8793}
8894
95+ // `virtualDiskInfo` struct is used to represent both GET_VIRTUAL_DISK_INFO
96+ // (https://learn.microsoft.com/en-us/windows/win32/api/virtdisk/ns-virtdisk-get_virtual_disk_info)
97+ // and SET_VIRTUAL_DISK_INFO
98+ // (https://learn.microsoft.com/en-us/windows/win32/api/virtdisk/ns-virtdisk-set_virtual_disk_info)
99+ // win32 types. Both of these win32 types have the same size and a very similar
100+ // structure. These types use tagged unions which aren't directly supported in Go, so we
101+ // keep this type unexported, and provide a cleaner interface to our callers by parsing
102+ // the data buffer for the right type.
103+ type virtualDiskInfo struct {
104+ version uint32
105+ _ [4 ]byte // padding
106+ data [24 ]byte // union of various types
107+ }
108+
89109const (
90110 //revive:disable-next-line:var-naming ALL_CAPS
91111 VIRTUAL_STORAGE_TYPE_DEVICE_VHDX = 0x3
@@ -143,6 +163,34 @@ const (
143163
144164 // Flags for detaching a VHD.
145165 DetachVirtualDiskFlagNone DetachVirtualDiskFlag = 0x0
166+
167+ // Flags for setting information about a VHD - these should remain unexported as we provide APIs to directly get/set a particular field.
168+ setVirtualDiskInfoUnspecified uint32 = 0x0
169+ setVirtualDiskInfoParentPath uint32 = 0x1
170+ setVirtualDiskInfoIdentifier uint32 = 0x2
171+ setVirtualDiskInfoParentPathWithDepth uint32 = 0x3
172+ setVirtualDiskInfoPhysicalSectorSize uint32 = 0x4
173+ setVirtualDiskInfoVirtualDiskId uint32 = 0x5
174+ setVirtualDiskInfoChangeTrackingState uint32 = 0x6
175+ setVirtualDiskInfoParentLocator uint32 = 0x7
176+
177+ // Flags for getting information about a VHD - these should remain unexported as we provide APIs to directly get/set a particular field.
178+ getVirtualDiskInfoUnspecified uint32 = 0x0
179+ getVirtualDiskInfoSize uint32 = 0x1
180+ getVirtualDiskInfoIdentifier uint32 = 0x2
181+ getVirtualDiskInfoParentLocation uint32 = 0x3
182+ getVirtualDiskInfoParentIdentifier uint32 = 0x4
183+ getVirtualDiskInfoParentTimestamp uint32 = 0x5
184+ getVirtualDiskInfoVirtualStorageType uint32 = 0x6
185+ getVirtualDiskInfoProviderSubtype uint32 = 0x7
186+ getVirtualDiskInfoIs4kAligned uint32 = 0x8
187+ getVirtualDiskInfoPhysicalDisk uint32 = 0x9
188+ getVirtualDiskInfoVhdPhysicalSectorSize uint32 = 0xA
189+ getVirtualDiskInfoSmallestSafeVirtualSize uint32 = 0xB
190+ getVirtualDiskInfoFragmentation uint32 = 0xC
191+ getVirtualDiskInfoIsLoaded uint32 = 0xD
192+ getVirtualDiskInfoVirtualDiskId uint32 = 0xE
193+ getVirtualDiskInfoChangeTrackingState uint32 = 0xF
146194)
147195
148196// CreateVhdx is a helper function to create a simple vhdx file at the given path using
@@ -375,3 +423,60 @@ func CreateDiffVhd(diffVhdPath, baseVhdPath string, blockSizeInMB uint32) error
375423 }
376424 return nil
377425}
426+
427+ // SetVirtualDiskIdentifier sets the virtual disk identifier for the specified virtual disk.
428+ func SetVirtualDiskIdentifier (vhdPath string , identifier guid.GUID ) error {
429+ handle , err := OpenVirtualDisk (vhdPath , VirtualDiskAccessNone , OpenVirtualDiskFlagNone )
430+ if err != nil {
431+ return fmt .Errorf ("failed to open %s: %w" , vhdPath , err )
432+ }
433+ defer syscall .Close (handle )
434+
435+ info := & virtualDiskInfo {
436+ version : setVirtualDiskInfoIdentifier ,
437+ }
438+ if strings .HasSuffix (vhdPath , ".vhdx" ) {
439+ // VHDx requires a different version to set disk id
440+ info .version = setVirtualDiskInfoVirtualDiskId
441+ }
442+
443+ if _ , err := binary .Encode (info .data [:], binary .LittleEndian , identifier ); err != nil {
444+ return fmt .Errorf ("failed to serialize virtual disk identifier: %w" , err )
445+ }
446+
447+ if err := setVirtualDiskInformation (handle , info ); err != nil {
448+ return fmt .Errorf ("failed to set virtual disk identifier: %w" , err )
449+ }
450+ return nil
451+ }
452+
453+ // GetVirtualDiskIdentifier retrieves the virtual disk identifier for the specified virtual disk.
454+ func GetVirtualDiskIdentifier (vhdPath string ) (guid.GUID , error ) {
455+ handle , err := OpenVirtualDisk (vhdPath , VirtualDiskAccessNone , OpenVirtualDiskFlagNone )
456+ if err != nil {
457+ return guid.GUID {}, fmt .Errorf ("failed to open %s: %w" , vhdPath , err )
458+ }
459+ defer syscall .Close (handle )
460+
461+ info := & virtualDiskInfo {
462+ version : getVirtualDiskInfoVirtualDiskId ,
463+ }
464+ if strings .HasSuffix (vhdPath , ".vhdx" ) {
465+ // VHDx requires a different version to get disk id
466+ info .version = getVirtualDiskInfoVirtualDiskId
467+ }
468+
469+ var sizeUsed uint32
470+ bufferSize := uint32 (unsafe .Sizeof (* info ))
471+ if err := getVirtualDiskInformation (handle , & bufferSize , info , & sizeUsed ); err != nil {
472+ return guid.GUID {}, fmt .Errorf ("failed to get virtual disk identifier: %w" , err )
473+ }
474+
475+ // Parse the response
476+ id := & guid.GUID {}
477+ reader := bytes .NewReader (info .data [:])
478+ if err := binary .Read (reader , binary .LittleEndian , id ); err != nil {
479+ return guid.GUID {}, fmt .Errorf ("failed to parse virtual disk identifier: %w" , err )
480+ }
481+ return * id , nil
482+ }
0 commit comments