Skip to content

Commit c33ebee

Browse files
committed
Add Go wrappers for Get/SetVirtualDiskInformation win32 APIs
SetVirtualDiskInformation API is required for running confidential windows containers. The VHDs used for starting confidential pods/UVMs need to have a specific disk identifier. These newly added APIs will be used when preparing the VHDs for confidential pods. Signed-off-by: Amit <[email protected]>
1 parent 092e983 commit c33ebee

File tree

3 files changed

+182
-0
lines changed

3 files changed

+182
-0
lines changed

vhd/vhd.go

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,12 @@
33
package vhd
44

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

2127
type (
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+
88108
const (
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+
}

vhd/vhd_test.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
//go:build windows
2+
3+
package vhd
4+
5+
import (
6+
"os"
7+
"path/filepath"
8+
"testing"
9+
10+
"github.com/Microsoft/go-winio/pkg/guid"
11+
)
12+
13+
func TestVirtualDiskIdentifier(t *testing.T) {
14+
// Create a temporary directory for the test
15+
tempDir := t.TempDir()
16+
// TODO(ambarve): We should add a test for VHD too, but our current create VHD API
17+
// seem to only work for VHDX.
18+
vhdPath := filepath.Join(tempDir, "test.vhdx")
19+
20+
// Create the virtual disk
21+
if err := CreateVhdx(vhdPath, 1, 1); err != nil { // 1GB, 1MB block size
22+
t.Fatalf("failed to create virtual disk: %s", err)
23+
}
24+
defer os.Remove(vhdPath)
25+
26+
// Get the initial identifier
27+
initialID, err := GetVirtualDiskIdentifier(vhdPath)
28+
if err != nil {
29+
t.Fatalf("failed to get initial virtual disk identifier: %s", err)
30+
}
31+
t.Logf("initial identifier: %s", initialID.String())
32+
33+
// Create a new GUID to set
34+
newID := guid.GUID{
35+
Data1: 0x12345678,
36+
Data2: 0x1234,
37+
Data3: 0x5678,
38+
Data4: [8]byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0},
39+
}
40+
t.Logf("setting new identifier: %s", newID.String())
41+
42+
// Set the new identifier
43+
if err := SetVirtualDiskIdentifier(vhdPath, newID); err != nil {
44+
t.Fatalf("failed to set virtual disk identifier: %s", err)
45+
}
46+
47+
// Get the identifier again to verify it was set correctly
48+
retrievedID, err := GetVirtualDiskIdentifier(vhdPath)
49+
if err != nil {
50+
t.Fatalf("failed to get virtual disk identifier after setting: %s", err)
51+
}
52+
t.Logf("retrieved identifier: %s", retrievedID.String())
53+
54+
// Verify the retrieved ID matches the one we set
55+
if retrievedID != newID {
56+
t.Errorf("retrieved identifier does not match set identifier.\nExpected: %s\nGot: %s",
57+
newID.String(), retrievedID.String())
58+
}
59+
}

vhd/zvhd_windows.go

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)