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