vhd.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  1. //go:build windows
  2. // +build windows
  3. package vhd
  4. import (
  5. "fmt"
  6. "syscall"
  7. "github.com/Microsoft/go-winio/pkg/guid"
  8. "golang.org/x/sys/windows"
  9. )
  10. //go:generate go run github.com/Microsoft/go-winio/tools/mkwinsyscall -output zvhd_windows.go vhd.go
  11. //sys createVirtualDisk(virtualStorageType *VirtualStorageType, path string, virtualDiskAccessMask uint32, securityDescriptor *uintptr, createVirtualDiskFlags uint32, providerSpecificFlags uint32, parameters *CreateVirtualDiskParameters, overlapped *syscall.Overlapped, handle *syscall.Handle) (win32err error) = virtdisk.CreateVirtualDisk
  12. //sys openVirtualDisk(virtualStorageType *VirtualStorageType, path string, virtualDiskAccessMask uint32, openVirtualDiskFlags uint32, parameters *openVirtualDiskParameters, handle *syscall.Handle) (win32err error) = virtdisk.OpenVirtualDisk
  13. //sys attachVirtualDisk(handle syscall.Handle, securityDescriptor *uintptr, attachVirtualDiskFlag uint32, providerSpecificFlags uint32, parameters *AttachVirtualDiskParameters, overlapped *syscall.Overlapped) (win32err error) = virtdisk.AttachVirtualDisk
  14. //sys detachVirtualDisk(handle syscall.Handle, detachVirtualDiskFlags uint32, providerSpecificFlags uint32) (win32err error) = virtdisk.DetachVirtualDisk
  15. //sys getVirtualDiskPhysicalPath(handle syscall.Handle, diskPathSizeInBytes *uint32, buffer *uint16) (win32err error) = virtdisk.GetVirtualDiskPhysicalPath
  16. type (
  17. CreateVirtualDiskFlag uint32
  18. VirtualDiskFlag uint32
  19. AttachVirtualDiskFlag uint32
  20. DetachVirtualDiskFlag uint32
  21. VirtualDiskAccessMask uint32
  22. )
  23. type VirtualStorageType struct {
  24. DeviceID uint32
  25. VendorID guid.GUID
  26. }
  27. type CreateVersion2 struct {
  28. UniqueID guid.GUID
  29. MaximumSize uint64
  30. BlockSizeInBytes uint32
  31. SectorSizeInBytes uint32
  32. PhysicalSectorSizeInByte uint32
  33. ParentPath *uint16 // string
  34. SourcePath *uint16 // string
  35. OpenFlags uint32
  36. ParentVirtualStorageType VirtualStorageType
  37. SourceVirtualStorageType VirtualStorageType
  38. ResiliencyGUID guid.GUID
  39. }
  40. type CreateVirtualDiskParameters struct {
  41. Version uint32 // Must always be set to 2
  42. Version2 CreateVersion2
  43. }
  44. type OpenVersion2 struct {
  45. GetInfoOnly bool
  46. ReadOnly bool
  47. ResiliencyGUID guid.GUID
  48. }
  49. type OpenVirtualDiskParameters struct {
  50. Version uint32 // Must always be set to 2
  51. Version2 OpenVersion2
  52. }
  53. // The higher level `OpenVersion2` struct uses `bool`s to refer to `GetInfoOnly` and `ReadOnly` for ease of use. However,
  54. // the internal windows structure uses `BOOL`s aka int32s for these types. `openVersion2` is used for translating
  55. // `OpenVersion2` fields to the correct windows internal field types on the `Open____` methods.
  56. type openVersion2 struct {
  57. getInfoOnly int32
  58. readOnly int32
  59. resiliencyGUID guid.GUID
  60. }
  61. type openVirtualDiskParameters struct {
  62. version uint32
  63. version2 openVersion2
  64. }
  65. type AttachVersion2 struct {
  66. RestrictedOffset uint64
  67. RestrictedLength uint64
  68. }
  69. type AttachVirtualDiskParameters struct {
  70. Version uint32
  71. Version2 AttachVersion2
  72. }
  73. const (
  74. //revive:disable-next-line:var-naming ALL_CAPS
  75. VIRTUAL_STORAGE_TYPE_DEVICE_VHDX = 0x3
  76. // Access Mask for opening a VHD.
  77. VirtualDiskAccessNone VirtualDiskAccessMask = 0x00000000
  78. VirtualDiskAccessAttachRO VirtualDiskAccessMask = 0x00010000
  79. VirtualDiskAccessAttachRW VirtualDiskAccessMask = 0x00020000
  80. VirtualDiskAccessDetach VirtualDiskAccessMask = 0x00040000
  81. VirtualDiskAccessGetInfo VirtualDiskAccessMask = 0x00080000
  82. VirtualDiskAccessCreate VirtualDiskAccessMask = 0x00100000
  83. VirtualDiskAccessMetaOps VirtualDiskAccessMask = 0x00200000
  84. VirtualDiskAccessRead VirtualDiskAccessMask = 0x000d0000
  85. VirtualDiskAccessAll VirtualDiskAccessMask = 0x003f0000
  86. VirtualDiskAccessWritable VirtualDiskAccessMask = 0x00320000
  87. // Flags for creating a VHD.
  88. CreateVirtualDiskFlagNone CreateVirtualDiskFlag = 0x0
  89. CreateVirtualDiskFlagFullPhysicalAllocation CreateVirtualDiskFlag = 0x1
  90. CreateVirtualDiskFlagPreventWritesToSourceDisk CreateVirtualDiskFlag = 0x2
  91. CreateVirtualDiskFlagDoNotCopyMetadataFromParent CreateVirtualDiskFlag = 0x4
  92. CreateVirtualDiskFlagCreateBackingStorage CreateVirtualDiskFlag = 0x8
  93. CreateVirtualDiskFlagUseChangeTrackingSourceLimit CreateVirtualDiskFlag = 0x10
  94. CreateVirtualDiskFlagPreserveParentChangeTrackingState CreateVirtualDiskFlag = 0x20
  95. CreateVirtualDiskFlagVhdSetUseOriginalBackingStorage CreateVirtualDiskFlag = 0x40 //revive:disable-line:var-naming VHD, not Vhd
  96. CreateVirtualDiskFlagSparseFile CreateVirtualDiskFlag = 0x80
  97. CreateVirtualDiskFlagPmemCompatible CreateVirtualDiskFlag = 0x100 //revive:disable-line:var-naming PMEM, not Pmem
  98. CreateVirtualDiskFlagSupportCompressedVolumes CreateVirtualDiskFlag = 0x200
  99. // Flags for opening a VHD.
  100. OpenVirtualDiskFlagNone VirtualDiskFlag = 0x00000000
  101. OpenVirtualDiskFlagNoParents VirtualDiskFlag = 0x00000001
  102. OpenVirtualDiskFlagBlankFile VirtualDiskFlag = 0x00000002
  103. OpenVirtualDiskFlagBootDrive VirtualDiskFlag = 0x00000004
  104. OpenVirtualDiskFlagCachedIO VirtualDiskFlag = 0x00000008
  105. OpenVirtualDiskFlagCustomDiffChain VirtualDiskFlag = 0x00000010
  106. OpenVirtualDiskFlagParentCachedIO VirtualDiskFlag = 0x00000020
  107. OpenVirtualDiskFlagVhdsetFileOnly VirtualDiskFlag = 0x00000040
  108. OpenVirtualDiskFlagIgnoreRelativeParentLocator VirtualDiskFlag = 0x00000080
  109. OpenVirtualDiskFlagNoWriteHardening VirtualDiskFlag = 0x00000100
  110. OpenVirtualDiskFlagSupportCompressedVolumes VirtualDiskFlag = 0x00000200
  111. // Flags for attaching a VHD.
  112. AttachVirtualDiskFlagNone AttachVirtualDiskFlag = 0x00000000
  113. AttachVirtualDiskFlagReadOnly AttachVirtualDiskFlag = 0x00000001
  114. AttachVirtualDiskFlagNoDriveLetter AttachVirtualDiskFlag = 0x00000002
  115. AttachVirtualDiskFlagPermanentLifetime AttachVirtualDiskFlag = 0x00000004
  116. AttachVirtualDiskFlagNoLocalHost AttachVirtualDiskFlag = 0x00000008
  117. AttachVirtualDiskFlagNoSecurityDescriptor AttachVirtualDiskFlag = 0x00000010
  118. AttachVirtualDiskFlagBypassDefaultEncryptionPolicy AttachVirtualDiskFlag = 0x00000020
  119. AttachVirtualDiskFlagNonPnp AttachVirtualDiskFlag = 0x00000040
  120. AttachVirtualDiskFlagRestrictedRange AttachVirtualDiskFlag = 0x00000080
  121. AttachVirtualDiskFlagSinglePartition AttachVirtualDiskFlag = 0x00000100
  122. AttachVirtualDiskFlagRegisterVolume AttachVirtualDiskFlag = 0x00000200
  123. // Flags for detaching a VHD.
  124. DetachVirtualDiskFlagNone DetachVirtualDiskFlag = 0x0
  125. )
  126. // CreateVhdx is a helper function to create a simple vhdx file at the given path using
  127. // default values.
  128. //
  129. //revive:disable-next-line:var-naming VHDX, not Vhdx
  130. func CreateVhdx(path string, maxSizeInGb, blockSizeInMb uint32) error {
  131. params := CreateVirtualDiskParameters{
  132. Version: 2,
  133. Version2: CreateVersion2{
  134. MaximumSize: uint64(maxSizeInGb) * 1024 * 1024 * 1024,
  135. BlockSizeInBytes: blockSizeInMb * 1024 * 1024,
  136. },
  137. }
  138. handle, err := CreateVirtualDisk(path, VirtualDiskAccessNone, CreateVirtualDiskFlagNone, &params)
  139. if err != nil {
  140. return err
  141. }
  142. return syscall.CloseHandle(handle)
  143. }
  144. // DetachVirtualDisk detaches a virtual hard disk by handle.
  145. func DetachVirtualDisk(handle syscall.Handle) (err error) {
  146. if err := detachVirtualDisk(handle, 0, 0); err != nil {
  147. return fmt.Errorf("failed to detach virtual disk: %w", err)
  148. }
  149. return nil
  150. }
  151. // DetachVhd detaches a vhd found at `path`.
  152. //
  153. //revive:disable-next-line:var-naming VHD, not Vhd
  154. func DetachVhd(path string) error {
  155. handle, err := OpenVirtualDisk(
  156. path,
  157. VirtualDiskAccessNone,
  158. OpenVirtualDiskFlagCachedIO|OpenVirtualDiskFlagIgnoreRelativeParentLocator,
  159. )
  160. if err != nil {
  161. return err
  162. }
  163. defer syscall.CloseHandle(handle) //nolint:errcheck
  164. return DetachVirtualDisk(handle)
  165. }
  166. // AttachVirtualDisk attaches a virtual hard disk for use.
  167. func AttachVirtualDisk(
  168. handle syscall.Handle,
  169. attachVirtualDiskFlag AttachVirtualDiskFlag,
  170. parameters *AttachVirtualDiskParameters,
  171. ) (err error) {
  172. // Supports both version 1 and 2 of the attach parameters as version 2 wasn't present in RS5.
  173. if err := attachVirtualDisk(
  174. handle,
  175. nil,
  176. uint32(attachVirtualDiskFlag),
  177. 0,
  178. parameters,
  179. nil,
  180. ); err != nil {
  181. return fmt.Errorf("failed to attach virtual disk: %w", err)
  182. }
  183. return nil
  184. }
  185. // AttachVhd attaches a virtual hard disk at `path` for use. Attaches using version 2
  186. // of the ATTACH_VIRTUAL_DISK_PARAMETERS.
  187. //
  188. //revive:disable-next-line:var-naming VHD, not Vhd
  189. func AttachVhd(path string) (err error) {
  190. handle, err := OpenVirtualDisk(
  191. path,
  192. VirtualDiskAccessNone,
  193. OpenVirtualDiskFlagCachedIO|OpenVirtualDiskFlagIgnoreRelativeParentLocator,
  194. )
  195. if err != nil {
  196. return err
  197. }
  198. defer syscall.CloseHandle(handle) //nolint:errcheck
  199. params := AttachVirtualDiskParameters{Version: 2}
  200. if err := AttachVirtualDisk(
  201. handle,
  202. AttachVirtualDiskFlagNone,
  203. &params,
  204. ); err != nil {
  205. return fmt.Errorf("failed to attach virtual disk: %w", err)
  206. }
  207. return nil
  208. }
  209. // OpenVirtualDisk obtains a handle to a VHD opened with supplied access mask and flags.
  210. func OpenVirtualDisk(
  211. vhdPath string,
  212. virtualDiskAccessMask VirtualDiskAccessMask,
  213. openVirtualDiskFlags VirtualDiskFlag,
  214. ) (syscall.Handle, error) {
  215. parameters := OpenVirtualDiskParameters{Version: 2}
  216. handle, err := OpenVirtualDiskWithParameters(
  217. vhdPath,
  218. virtualDiskAccessMask,
  219. openVirtualDiskFlags,
  220. &parameters,
  221. )
  222. if err != nil {
  223. return 0, err
  224. }
  225. return handle, nil
  226. }
  227. // OpenVirtualDiskWithParameters obtains a handle to a VHD opened with supplied access mask, flags and parameters.
  228. func OpenVirtualDiskWithParameters(
  229. vhdPath string,
  230. virtualDiskAccessMask VirtualDiskAccessMask,
  231. openVirtualDiskFlags VirtualDiskFlag,
  232. parameters *OpenVirtualDiskParameters,
  233. ) (syscall.Handle, error) {
  234. var (
  235. handle syscall.Handle
  236. defaultType VirtualStorageType
  237. getInfoOnly int32
  238. readOnly int32
  239. )
  240. if parameters.Version != 2 {
  241. return handle, fmt.Errorf("only version 2 VHDs are supported, found version: %d", parameters.Version)
  242. }
  243. if parameters.Version2.GetInfoOnly {
  244. getInfoOnly = 1
  245. }
  246. if parameters.Version2.ReadOnly {
  247. readOnly = 1
  248. }
  249. params := &openVirtualDiskParameters{
  250. version: parameters.Version,
  251. version2: openVersion2{
  252. getInfoOnly,
  253. readOnly,
  254. parameters.Version2.ResiliencyGUID,
  255. },
  256. }
  257. if err := openVirtualDisk(
  258. &defaultType,
  259. vhdPath,
  260. uint32(virtualDiskAccessMask),
  261. uint32(openVirtualDiskFlags),
  262. params,
  263. &handle,
  264. ); err != nil {
  265. return 0, fmt.Errorf("failed to open virtual disk: %w", err)
  266. }
  267. return handle, nil
  268. }
  269. // CreateVirtualDisk creates a virtual harddisk and returns a handle to the disk.
  270. func CreateVirtualDisk(
  271. path string,
  272. virtualDiskAccessMask VirtualDiskAccessMask,
  273. createVirtualDiskFlags CreateVirtualDiskFlag,
  274. parameters *CreateVirtualDiskParameters,
  275. ) (syscall.Handle, error) {
  276. var (
  277. handle syscall.Handle
  278. defaultType VirtualStorageType
  279. )
  280. if parameters.Version != 2 {
  281. return handle, fmt.Errorf("only version 2 VHDs are supported, found version: %d", parameters.Version)
  282. }
  283. if err := createVirtualDisk(
  284. &defaultType,
  285. path,
  286. uint32(virtualDiskAccessMask),
  287. nil,
  288. uint32(createVirtualDiskFlags),
  289. 0,
  290. parameters,
  291. nil,
  292. &handle,
  293. ); err != nil {
  294. return handle, fmt.Errorf("failed to create virtual disk: %w", err)
  295. }
  296. return handle, nil
  297. }
  298. // GetVirtualDiskPhysicalPath takes a handle to a virtual hard disk and returns the physical
  299. // path of the disk on the machine. This path is in the form \\.\PhysicalDriveX where X is an integer
  300. // that represents the particular enumeration of the physical disk on the caller's system.
  301. func GetVirtualDiskPhysicalPath(handle syscall.Handle) (_ string, err error) {
  302. var (
  303. diskPathSizeInBytes uint32 = 256 * 2 // max path length 256 wide chars
  304. diskPhysicalPathBuf [256]uint16
  305. )
  306. if err := getVirtualDiskPhysicalPath(
  307. handle,
  308. &diskPathSizeInBytes,
  309. &diskPhysicalPathBuf[0],
  310. ); err != nil {
  311. return "", fmt.Errorf("failed to get disk physical path: %w", err)
  312. }
  313. return windows.UTF16ToString(diskPhysicalPathBuf[:]), nil
  314. }
  315. // CreateDiffVhd is a helper function to create a differencing virtual disk.
  316. //
  317. //revive:disable-next-line:var-naming VHD, not Vhd
  318. func CreateDiffVhd(diffVhdPath, baseVhdPath string, blockSizeInMB uint32) error {
  319. // Setting `ParentPath` is how to signal to create a differencing disk.
  320. createParams := &CreateVirtualDiskParameters{
  321. Version: 2,
  322. Version2: CreateVersion2{
  323. ParentPath: windows.StringToUTF16Ptr(baseVhdPath),
  324. BlockSizeInBytes: blockSizeInMB * 1024 * 1024,
  325. OpenFlags: uint32(OpenVirtualDiskFlagCachedIO),
  326. },
  327. }
  328. vhdHandle, err := CreateVirtualDisk(
  329. diffVhdPath,
  330. VirtualDiskAccessNone,
  331. CreateVirtualDiskFlagNone,
  332. createParams,
  333. )
  334. if err != nil {
  335. return fmt.Errorf("failed to create differencing vhd: %w", err)
  336. }
  337. if err := syscall.CloseHandle(vhdHandle); err != nil {
  338. return fmt.Errorf("failed to close differencing vhd handle: %w", err)
  339. }
  340. return nil
  341. }