2
2
mirror of https://github.com/octoleo/restic.git synced 2024-11-14 01:04:05 +00:00
restic/internal/fs/vss_windows.go
fgma 5695f9ebd2 vss: Implement VSS support for Windows
The VSS support works for 32 and 64-bit windows, this includes a check that
the restic version matches the OS architecture as required by VSS. The backup
operation will fail the user has not sufficient permissions to use VSS.

Snapshotting volumes also covers mountpoints but skips UNC paths.
2020-10-24 11:35:57 +02:00

1067 lines
38 KiB
Go

// +build windows
package fs
import (
"fmt"
"path/filepath"
"runtime"
"strings"
"syscall"
"unsafe"
ole "github.com/go-ole/go-ole"
"github.com/restic/restic/internal/errors"
"golang.org/x/sys/windows"
)
// HRESULT is a custom type for the windows api HRESULT type.
type HRESULT uint
// HRESULT constant values necessary for using VSS api.
const (
S_OK HRESULT = 0x00000000
E_ACCESSDENIED HRESULT = 0x80070005
E_OUTOFMEMORY HRESULT = 0x8007000E
E_INVALIDARG HRESULT = 0x80070057
VSS_E_BAD_STATE HRESULT = 0x80042301
VSS_E_UNEXPECTED HRESULT = 0x80042302
VSS_E_PROVIDER_ALREADY_REGISTERED HRESULT = 0x80042303
VSS_E_PROVIDER_NOT_REGISTERED HRESULT = 0x80042304
VSS_E_PROVIDER_VETO HRESULT = 0x80042306
VSS_E_PROVIDER_IN_USE HRESULT = 0x80042307
VSS_E_OBJECT_NOT_FOUND HRESULT = 0x80042308
VSS_E_VOLUME_NOT_SUPPORTED HRESULT = 0x8004230C
VSS_E_VOLUME_NOT_SUPPORTED_BY_PROVIDER HRESULT = 0x8004230E
VSS_E_OBJECT_ALREADY_EXISTS HRESULT = 0x8004230D
VSS_E_UNEXPECTED_PROVIDER_ERROR HRESULT = 0x8004230F
VSS_E_CORRUPT_XML_DOCUMENT HRESULT = 0x80042310
VSS_E_INVALID_XML_DOCUMENT HRESULT = 0x80042311
VSS_E_MAXIMUM_NUMBER_OF_VOLUMES_REACHED HRESULT = 0x80042312
VSS_E_FLUSH_WRITES_TIMEOUT HRESULT = 0x80042313
VSS_E_HOLD_WRITES_TIMEOUT HRESULT = 0x80042314
VSS_E_UNEXPECTED_WRITER_ERROR HRESULT = 0x80042315
VSS_E_SNAPSHOT_SET_IN_PROGRESS HRESULT = 0x80042316
VSS_E_MAXIMUM_NUMBER_OF_SNAPSHOTS_REACHED HRESULT = 0x80042317
VSS_E_WRITER_INFRASTRUCTURE HRESULT = 0x80042318
VSS_E_WRITER_NOT_RESPONDING HRESULT = 0x80042319
VSS_E_WRITER_ALREADY_SUBSCRIBED HRESULT = 0x8004231A
VSS_E_UNSUPPORTED_CONTEXT HRESULT = 0x8004231B
VSS_E_VOLUME_IN_USE HRESULT = 0x8004231D
VSS_E_MAXIMUM_DIFFAREA_ASSOCIATIONS_REACHED HRESULT = 0x8004231E
VSS_E_INSUFFICIENT_STORAGE HRESULT = 0x8004231F
VSS_E_NO_SNAPSHOTS_IMPORTED HRESULT = 0x80042320
VSS_E_SOME_SNAPSHOTS_NOT_IMPORTED HRESULT = 0x80042321
VSS_E_MAXIMUM_NUMBER_OF_REMOTE_MACHINES_REACHED HRESULT = 0x80042322
VSS_E_REMOTE_SERVER_UNAVAILABLE HRESULT = 0x80042323
VSS_E_REMOTE_SERVER_UNSUPPORTED HRESULT = 0x80042324
VSS_E_REVERT_IN_PROGRESS HRESULT = 0x80042325
VSS_E_REVERT_VOLUME_LOST HRESULT = 0x80042326
VSS_E_REBOOT_REQUIRED HRESULT = 0x80042327
VSS_E_TRANSACTION_FREEZE_TIMEOUT HRESULT = 0x80042328
VSS_E_TRANSACTION_THAW_TIMEOUT HRESULT = 0x80042329
VSS_E_VOLUME_NOT_LOCAL HRESULT = 0x8004232D
VSS_E_CLUSTER_TIMEOUT HRESULT = 0x8004232E
VSS_E_WRITERERROR_INCONSISTENTSNAPSHOT HRESULT = 0x800423F0
VSS_E_WRITERERROR_OUTOFRESOURCES HRESULT = 0x800423F1
VSS_E_WRITERERROR_TIMEOUT HRESULT = 0x800423F2
VSS_E_WRITERERROR_RETRYABLE HRESULT = 0x800423F3
VSS_E_WRITERERROR_NONRETRYABLE HRESULT = 0x800423F4
VSS_E_WRITERERROR_RECOVERY_FAILED HRESULT = 0x800423F5
VSS_E_BREAK_REVERT_ID_FAILED HRESULT = 0x800423F6
VSS_E_LEGACY_PROVIDER HRESULT = 0x800423F7
VSS_E_MISSING_DISK HRESULT = 0x800423F8
VSS_E_MISSING_HIDDEN_VOLUME HRESULT = 0x800423F9
VSS_E_MISSING_VOLUME HRESULT = 0x800423FA
VSS_E_AUTORECOVERY_FAILED HRESULT = 0x800423FB
VSS_E_DYNAMIC_DISK_ERROR HRESULT = 0x800423FC
VSS_E_NONTRANSPORTABLE_BCD HRESULT = 0x800423FD
VSS_E_CANNOT_REVERT_DISKID HRESULT = 0x800423FE
VSS_E_RESYNC_IN_PROGRESS HRESULT = 0x800423FF
VSS_E_CLUSTER_ERROR HRESULT = 0x80042400
VSS_E_UNSELECTED_VOLUME HRESULT = 0x8004232A
VSS_E_SNAPSHOT_NOT_IN_SET HRESULT = 0x8004232B
VSS_E_NESTED_VOLUME_LIMIT HRESULT = 0x8004232C
VSS_E_NOT_SUPPORTED HRESULT = 0x8004232F
VSS_E_WRITERERROR_PARTIAL_FAILURE HRESULT = 0x80042336
VSS_E_WRITER_STATUS_NOT_AVAILABLE HRESULT = 0x80042409
)
// hresultToString maps a HRESULT value to a human readable string.
var hresultToString = map[HRESULT]string{
S_OK: "S_OK",
E_ACCESSDENIED: "E_ACCESSDENIED",
E_OUTOFMEMORY: "E_OUTOFMEMORY",
E_INVALIDARG: "E_INVALIDARG",
VSS_E_BAD_STATE: "VSS_E_BAD_STATE",
VSS_E_UNEXPECTED: "VSS_E_UNEXPECTED",
VSS_E_PROVIDER_ALREADY_REGISTERED: "VSS_E_PROVIDER_ALREADY_REGISTERED",
VSS_E_PROVIDER_NOT_REGISTERED: "VSS_E_PROVIDER_NOT_REGISTERED",
VSS_E_PROVIDER_VETO: "VSS_E_PROVIDER_VETO",
VSS_E_PROVIDER_IN_USE: "VSS_E_PROVIDER_IN_USE",
VSS_E_OBJECT_NOT_FOUND: "VSS_E_OBJECT_NOT_FOUND",
VSS_E_VOLUME_NOT_SUPPORTED: "VSS_E_VOLUME_NOT_SUPPORTED",
VSS_E_VOLUME_NOT_SUPPORTED_BY_PROVIDER: "VSS_E_VOLUME_NOT_SUPPORTED_BY_PROVIDER",
VSS_E_OBJECT_ALREADY_EXISTS: "VSS_E_OBJECT_ALREADY_EXISTS",
VSS_E_UNEXPECTED_PROVIDER_ERROR: "VSS_E_UNEXPECTED_PROVIDER_ERROR",
VSS_E_CORRUPT_XML_DOCUMENT: "VSS_E_CORRUPT_XML_DOCUMENT",
VSS_E_INVALID_XML_DOCUMENT: "VSS_E_INVALID_XML_DOCUMENT",
VSS_E_MAXIMUM_NUMBER_OF_VOLUMES_REACHED: "VSS_E_MAXIMUM_NUMBER_OF_VOLUMES_REACHED",
VSS_E_FLUSH_WRITES_TIMEOUT: "VSS_E_FLUSH_WRITES_TIMEOUT",
VSS_E_HOLD_WRITES_TIMEOUT: "VSS_E_HOLD_WRITES_TIMEOUT",
VSS_E_UNEXPECTED_WRITER_ERROR: "VSS_E_UNEXPECTED_WRITER_ERROR",
VSS_E_SNAPSHOT_SET_IN_PROGRESS: "VSS_E_SNAPSHOT_SET_IN_PROGRESS",
VSS_E_MAXIMUM_NUMBER_OF_SNAPSHOTS_REACHED: "VSS_E_MAXIMUM_NUMBER_OF_SNAPSHOTS_REACHED",
VSS_E_WRITER_INFRASTRUCTURE: "VSS_E_WRITER_INFRASTRUCTURE",
VSS_E_WRITER_NOT_RESPONDING: "VSS_E_WRITER_NOT_RESPONDING",
VSS_E_WRITER_ALREADY_SUBSCRIBED: "VSS_E_WRITER_ALREADY_SUBSCRIBED",
VSS_E_UNSUPPORTED_CONTEXT: "VSS_E_UNSUPPORTED_CONTEXT",
VSS_E_VOLUME_IN_USE: "VSS_E_VOLUME_IN_USE",
VSS_E_MAXIMUM_DIFFAREA_ASSOCIATIONS_REACHED: "VSS_E_MAXIMUM_DIFFAREA_ASSOCIATIONS_REACHED",
VSS_E_INSUFFICIENT_STORAGE: "VSS_E_INSUFFICIENT_STORAGE",
VSS_E_NO_SNAPSHOTS_IMPORTED: "VSS_E_NO_SNAPSHOTS_IMPORTED",
VSS_E_SOME_SNAPSHOTS_NOT_IMPORTED: "VSS_E_SOME_SNAPSHOTS_NOT_IMPORTED",
VSS_E_MAXIMUM_NUMBER_OF_REMOTE_MACHINES_REACHED: "VSS_E_MAXIMUM_NUMBER_OF_REMOTE_MACHINES_REACHED",
VSS_E_REMOTE_SERVER_UNAVAILABLE: "VSS_E_REMOTE_SERVER_UNAVAILABLE",
VSS_E_REMOTE_SERVER_UNSUPPORTED: "VSS_E_REMOTE_SERVER_UNSUPPORTED",
VSS_E_REVERT_IN_PROGRESS: "VSS_E_REVERT_IN_PROGRESS",
VSS_E_REVERT_VOLUME_LOST: "VSS_E_REVERT_VOLUME_LOST",
VSS_E_REBOOT_REQUIRED: "VSS_E_REBOOT_REQUIRED",
VSS_E_TRANSACTION_FREEZE_TIMEOUT: "VSS_E_TRANSACTION_FREEZE_TIMEOUT",
VSS_E_TRANSACTION_THAW_TIMEOUT: "VSS_E_TRANSACTION_THAW_TIMEOUT",
VSS_E_VOLUME_NOT_LOCAL: "VSS_E_VOLUME_NOT_LOCAL",
VSS_E_CLUSTER_TIMEOUT: "VSS_E_CLUSTER_TIMEOUT",
VSS_E_WRITERERROR_INCONSISTENTSNAPSHOT: "VSS_E_WRITERERROR_INCONSISTENTSNAPSHOT",
VSS_E_WRITERERROR_OUTOFRESOURCES: "VSS_E_WRITERERROR_OUTOFRESOURCES",
VSS_E_WRITERERROR_TIMEOUT: "VSS_E_WRITERERROR_TIMEOUT",
VSS_E_WRITERERROR_RETRYABLE: "VSS_E_WRITERERROR_RETRYABLE",
VSS_E_WRITERERROR_NONRETRYABLE: "VSS_E_WRITERERROR_NONRETRYABLE",
VSS_E_WRITERERROR_RECOVERY_FAILED: "VSS_E_WRITERERROR_RECOVERY_FAILED",
VSS_E_BREAK_REVERT_ID_FAILED: "VSS_E_BREAK_REVERT_ID_FAILED",
VSS_E_LEGACY_PROVIDER: "VSS_E_LEGACY_PROVIDER",
VSS_E_MISSING_DISK: "VSS_E_MISSING_DISK",
VSS_E_MISSING_HIDDEN_VOLUME: "VSS_E_MISSING_HIDDEN_VOLUME",
VSS_E_MISSING_VOLUME: "VSS_E_MISSING_VOLUME",
VSS_E_AUTORECOVERY_FAILED: "VSS_E_AUTORECOVERY_FAILED",
VSS_E_DYNAMIC_DISK_ERROR: "VSS_E_DYNAMIC_DISK_ERROR",
VSS_E_NONTRANSPORTABLE_BCD: "VSS_E_NONTRANSPORTABLE_BCD",
VSS_E_CANNOT_REVERT_DISKID: "VSS_E_CANNOT_REVERT_DISKID",
VSS_E_RESYNC_IN_PROGRESS: "VSS_E_RESYNC_IN_PROGRESS",
VSS_E_CLUSTER_ERROR: "VSS_E_CLUSTER_ERROR",
VSS_E_UNSELECTED_VOLUME: "VSS_E_UNSELECTED_VOLUME",
VSS_E_SNAPSHOT_NOT_IN_SET: "VSS_E_SNAPSHOT_NOT_IN_SET",
VSS_E_NESTED_VOLUME_LIMIT: "VSS_E_NESTED_VOLUME_LIMIT",
VSS_E_NOT_SUPPORTED: "VSS_E_NOT_SUPPORTED",
VSS_E_WRITERERROR_PARTIAL_FAILURE: "VSS_E_WRITERERROR_PARTIAL_FAILURE",
VSS_E_WRITER_STATUS_NOT_AVAILABLE: "VSS_E_WRITER_STATUS_NOT_AVAILABLE",
}
// Str converts a HRESULT to a human readable string.
func (h HRESULT) Str() string {
if i, ok := hresultToString[h]; ok {
return i
}
return "UNKNOWN"
}
// VssError encapsulates errors retruned from calling VSS api.
type vssError struct {
text string
hresult HRESULT
}
// NewVssError creates a new VSS api error.
func newVssError(text string, hresult HRESULT) error {
return &vssError{text: text, hresult: hresult}
}
// NewVssError creates a new VSS api error.
func newVssErrorIfResultNotOK(text string, hresult HRESULT) error {
if hresult != S_OK {
return newVssError(text, hresult)
}
return nil
}
// Error implements the error interface.
func (e *vssError) Error() string {
return fmt.Sprintf("VSS error: %s: %s (%#x)", e.text, e.hresult.Str(), e.hresult)
}
// VssError encapsulates errors retruned from calling VSS api.
type vssTextError struct {
text string
}
// NewVssTextError creates a new VSS api error.
func newVssTextError(text string) error {
return &vssTextError{text: text}
}
// Error implements the error interface.
func (e *vssTextError) Error() string {
return fmt.Sprintf("VSS error: %s", e.text)
}
// VssContext is a custom type for the windows api VssContext type.
type VssContext uint
// VssContext constant values necessary for using VSS api.
const (
VSS_CTX_BACKUP VssContext = iota
VSS_CTX_FILE_SHARE_BACKUP
VSS_CTX_NAS_ROLLBACK
VSS_CTX_APP_ROLLBACK
VSS_CTX_CLIENT_ACCESSIBLE
VSS_CTX_CLIENT_ACCESSIBLE_WRITERS
VSS_CTX_ALL
)
// VssBackup is a custom type for the windows api VssBackup type.
type VssBackup uint
// VssBackup constant values necessary for using VSS api.
const (
VSS_BT_UNDEFINED VssBackup = iota
VSS_BT_FULL
VSS_BT_INCREMENTAL
VSS_BT_DIFFERENTIAL
VSS_BT_LOG
VSS_BT_COPY
VSS_BT_OTHER
)
// VssObjectType is a custom type for the windows api VssObjectType type.
type VssObjectType uint
// VssObjectType constant values necessary for using VSS api.
const (
VSS_OBJECT_UNKNOWN VssObjectType = iota
VSS_OBJECT_NONE
VSS_OBJECT_SNAPSHOT_SET
VSS_OBJECT_SNAPSHOT
VSS_OBJECT_PROVIDER
VSS_OBJECT_TYPE_COUNT
)
// UUID_IVSS defines the GUID of IVssBackupComponents.
var UUID_IVSS = ole.NewGUID("{665c1d5f-c218-414d-a05d-7fef5f9d5c86}")
// IVssBackupComponents VSS api interface.
type IVssBackupComponents struct {
ole.IUnknown
}
// IVssBackupComponentsVTable is the vtable for IVssBackupComponents.
type IVssBackupComponentsVTable struct {
ole.IUnknownVtbl
getWriterComponentsCount uintptr
getWriterComponents uintptr
initializeForBackup uintptr
setBackupState uintptr
initializeForRestore uintptr
setRestoreState uintptr
gatherWriterMetadata uintptr
getWriterMetadataCount uintptr
getWriterMetadata uintptr
freeWriterMetadata uintptr
addComponent uintptr
prepareForBackup uintptr
abortBackup uintptr
gatherWriterStatus uintptr
getWriterStatusCount uintptr
freeWriterStatus uintptr
getWriterStatus uintptr
setBackupSucceeded uintptr
setBackupOptions uintptr
setSelectedForRestore uintptr
setRestoreOptions uintptr
setAdditionalRestores uintptr
setPreviousBackupStamp uintptr
saveAsXML uintptr
backupComplete uintptr
addAlternativeLocationMapping uintptr
addRestoreSubcomponent uintptr
setFileRestoreStatus uintptr
addNewTarget uintptr
setRangesFilePath uintptr
preRestore uintptr
postRestore uintptr
setContext uintptr
startSnapshotSet uintptr
addToSnapshotSet uintptr
doSnapshotSet uintptr
deleteSnapshots uintptr
importSnapshots uintptr
breakSnapshotSet uintptr
getSnapshotProperties uintptr
query uintptr
isVolumeSupported uintptr
disableWriterClasses uintptr
enableWriterClasses uintptr
disableWriterInstances uintptr
exposeSnapshot uintptr
revertToSnapshot uintptr
queryRevertStatus uintptr
}
// getVTable returns the vtable for IVssBackupComponents.
func (vss *IVssBackupComponents) getVTable() *IVssBackupComponentsVTable {
return (*IVssBackupComponentsVTable)(unsafe.Pointer(vss.RawVTable))
}
// AbortBackup calls the equivalent VSS api.
func (vss *IVssBackupComponents) AbortBackup() error {
result, _, _ := syscall.Syscall(vss.getVTable().abortBackup, 1,
uintptr(unsafe.Pointer(vss)), 0, 0)
return newVssErrorIfResultNotOK("AbortBackup() failed", HRESULT(result))
}
// InitializeForBackup calls the equivalent VSS api.
func (vss *IVssBackupComponents) InitializeForBackup() error {
result, _, _ := syscall.Syscall(vss.getVTable().initializeForBackup, 2,
uintptr(unsafe.Pointer(vss)), 0, 0)
return newVssErrorIfResultNotOK("InitializeForBackup() failed", HRESULT(result))
}
// SetContext calls the equivalent VSS api.
func (vss *IVssBackupComponents) SetContext(context VssContext) error {
result, _, _ := syscall.Syscall(vss.getVTable().setContext, 2,
uintptr(unsafe.Pointer(vss)), uintptr(context), 0)
return newVssErrorIfResultNotOK("SetContext() failed", HRESULT(result))
}
// GatherWriterMetadata calls the equivalent VSS api.
func (vss *IVssBackupComponents) GatherWriterMetadata() (*IVSSAsync, error) {
var oleIUnknown *ole.IUnknown
result, _, _ := syscall.Syscall(vss.getVTable().gatherWriterMetadata, 2,
uintptr(unsafe.Pointer(vss)), uintptr(unsafe.Pointer(&oleIUnknown)), 0)
err := newVssErrorIfResultNotOK("GatherWriterMetadata() failed", HRESULT(result))
return vss.convertToVSSAsync(oleIUnknown, err)
}
// convertToVSSAsync looks up IVSSAsync interface if given result
// is a success.
func (vss *IVssBackupComponents) convertToVSSAsync(
oleIUnknown *ole.IUnknown, err error) (*IVSSAsync, error) {
if err != nil {
return nil, err
}
comInterface, err := queryInterface(oleIUnknown, UIID_IVSS_ASYNC)
if err != nil {
return nil, err
}
iVssAsync := (*IVSSAsync)(unsafe.Pointer(comInterface))
return iVssAsync, nil
}
// IsVolumeSupported calls the equivalent VSS api.
func (vss *IVssBackupComponents) IsVolumeSupported(volumeName string) (bool, error) {
volumeNamePointer, err := syscall.UTF16PtrFromString(volumeName)
if err != nil {
panic(err)
}
var isSupportedRaw uint32
var result uintptr
if runtime.GOARCH == "386" {
id := (*[4]uintptr)(unsafe.Pointer(ole.IID_NULL))
result, _, _ = syscall.Syscall9(vss.getVTable().isVolumeSupported, 7,
uintptr(unsafe.Pointer(vss)), id[0], id[1], id[2], id[3],
uintptr(unsafe.Pointer(volumeNamePointer)), uintptr(unsafe.Pointer(&isSupportedRaw)), 0,
0)
} else {
result, _, _ = syscall.Syscall6(vss.getVTable().isVolumeSupported, 4,
uintptr(unsafe.Pointer(vss)), uintptr(unsafe.Pointer(ole.IID_NULL)),
uintptr(unsafe.Pointer(volumeNamePointer)), uintptr(unsafe.Pointer(&isSupportedRaw)), 0,
0)
}
var isSupported bool
if isSupportedRaw == 0 {
isSupported = false
} else {
isSupported = true
}
return isSupported, newVssErrorIfResultNotOK("IsVolumeSupported() failed", HRESULT(result))
}
// StartSnapshotSet calls the equivalent VSS api.
func (vss *IVssBackupComponents) StartSnapshotSet() (ole.GUID, error) {
var snapshotSetID ole.GUID
result, _, _ := syscall.Syscall(vss.getVTable().startSnapshotSet, 2,
uintptr(unsafe.Pointer(vss)), uintptr(unsafe.Pointer(&snapshotSetID)), 0,
)
return snapshotSetID, newVssErrorIfResultNotOK("StartSnapshotSet() failed", HRESULT(result))
}
// AddToSnapshotSet calls the equivalent VSS api.
func (vss *IVssBackupComponents) AddToSnapshotSet(volumeName string, idSnapshot *ole.GUID) error {
volumeNamePointer, err := syscall.UTF16PtrFromString(volumeName)
if err != nil {
panic(err)
}
var result uintptr = 0
if runtime.GOARCH == "386" {
id := (*[4]uintptr)(unsafe.Pointer(ole.IID_NULL))
result, _, _ = syscall.Syscall9(vss.getVTable().addToSnapshotSet, 7,
uintptr(unsafe.Pointer(vss)), uintptr(unsafe.Pointer(volumeNamePointer)), id[0], id[1],
id[2], id[3], uintptr(unsafe.Pointer(idSnapshot)), 0, 0)
} else {
result, _, _ = syscall.Syscall6(vss.getVTable().addToSnapshotSet, 4,
uintptr(unsafe.Pointer(vss)), uintptr(unsafe.Pointer(volumeNamePointer)),
uintptr(unsafe.Pointer(ole.IID_NULL)), uintptr(unsafe.Pointer(idSnapshot)), 0, 0)
}
return newVssErrorIfResultNotOK("AddToSnapshotSet() failed", HRESULT(result))
}
// PrepareForBackup calls the equivalent VSS api.
func (vss *IVssBackupComponents) PrepareForBackup() (*IVSSAsync, error) {
var oleIUnknown *ole.IUnknown
result, _, _ := syscall.Syscall(vss.getVTable().prepareForBackup, 2,
uintptr(unsafe.Pointer(vss)), uintptr(unsafe.Pointer(&oleIUnknown)), 0)
err := newVssErrorIfResultNotOK("PrepareForBackup() failed", HRESULT(result))
return vss.convertToVSSAsync(oleIUnknown, err)
}
// apiBoolToInt converts a bool for use calling the VSS api
func apiBoolToInt(input bool) uint {
if input {
return 1
}
return 0
}
// SetBackupState calls the equivalent VSS api.
func (vss *IVssBackupComponents) SetBackupState(selectComponents bool,
backupBootableSystemState bool, backupType VssBackup, partialFileSupport bool,
) error {
selectComponentsVal := apiBoolToInt(selectComponents)
backupBootableSystemStateVal := apiBoolToInt(backupBootableSystemState)
partialFileSupportVal := apiBoolToInt(partialFileSupport)
result, _, _ := syscall.Syscall6(vss.getVTable().setBackupState, 5,
uintptr(unsafe.Pointer(vss)), uintptr(selectComponentsVal),
uintptr(backupBootableSystemStateVal), uintptr(backupType), uintptr(partialFileSupportVal),
0)
return newVssErrorIfResultNotOK("SetBackupState() failed", HRESULT(result))
}
// DoSnapshotSet calls the equivalent VSS api.
func (vss *IVssBackupComponents) DoSnapshotSet() (*IVSSAsync, error) {
var oleIUnknown *ole.IUnknown
result, _, _ := syscall.Syscall(vss.getVTable().doSnapshotSet, 2, uintptr(unsafe.Pointer(vss)),
uintptr(unsafe.Pointer(&oleIUnknown)), 0)
err := newVssErrorIfResultNotOK("DoSnapshotSet() failed", HRESULT(result))
return vss.convertToVSSAsync(oleIUnknown, err)
}
// DeleteSnapshots calls the equivalent VSS api.
func (vss *IVssBackupComponents) DeleteSnapshots(snapshotID ole.GUID) (int32, ole.GUID, error) {
var deletedSnapshots int32 = 0
var nondeletedSnapshotID ole.GUID
var result uintptr = 0
if runtime.GOARCH == "386" {
id := (*[4]uintptr)(unsafe.Pointer(ole.IID_NULL))
result, _, _ = syscall.Syscall9(vss.getVTable().deleteSnapshots, 9,
uintptr(unsafe.Pointer(vss)), id[0], id[1], id[2], id[3],
uintptr(VSS_OBJECT_SNAPSHOT), uintptr(1), uintptr(unsafe.Pointer(&deletedSnapshots)),
uintptr(unsafe.Pointer(&nondeletedSnapshotID)),
)
} else {
result, _, _ = syscall.Syscall6(vss.getVTable().deleteSnapshots, 6,
uintptr(unsafe.Pointer(vss)), uintptr(unsafe.Pointer(&snapshotID)),
uintptr(VSS_OBJECT_SNAPSHOT), uintptr(1), uintptr(unsafe.Pointer(&deletedSnapshots)),
uintptr(unsafe.Pointer(&nondeletedSnapshotID)))
}
err := newVssErrorIfResultNotOK("DeleteSnapshots() failed", HRESULT(result))
return deletedSnapshots, nondeletedSnapshotID, err
}
// GetSnapshotProperties calls the equivalent VSS api.
func (vss *IVssBackupComponents) GetSnapshotProperties(snapshotID ole.GUID,
properties *VssSnapshotProperties) error {
var result uintptr = 0
if runtime.GOARCH == "386" {
id := (*[4]uintptr)(unsafe.Pointer(ole.IID_NULL))
result, _, _ = syscall.Syscall6(vss.getVTable().getSnapshotProperties, 6,
uintptr(unsafe.Pointer(vss)), id[0], id[1], id[2], id[3],
uintptr(unsafe.Pointer(properties)))
} else {
result, _, _ = syscall.Syscall(vss.getVTable().getSnapshotProperties, 3,
uintptr(unsafe.Pointer(vss)), uintptr(unsafe.Pointer(&snapshotID)),
uintptr(unsafe.Pointer(properties)))
}
return newVssErrorIfResultNotOK("GetSnapshotProperties() failed", HRESULT(result))
}
// vssFreeSnapshotProperties calls the equivalent VSS api.
func vssFreeSnapshotProperties(properties *VssSnapshotProperties) error {
proc, err := findVssProc("VssFreeSnapshotProperties")
if err != nil {
return err
}
proc.Call(uintptr(unsafe.Pointer(properties)))
return nil
}
// BackupComplete calls the equivalent VSS api.
func (vss *IVssBackupComponents) BackupComplete() (*IVSSAsync, error) {
var oleIUnknown *ole.IUnknown
result, _, _ := syscall.Syscall(vss.getVTable().backupComplete, 2, uintptr(unsafe.Pointer(vss)),
uintptr(unsafe.Pointer(&oleIUnknown)), 0)
err := newVssErrorIfResultNotOK("BackupComplete() failed", HRESULT(result))
return vss.convertToVSSAsync(oleIUnknown, err)
}
// VssSnapshotProperties defines the properties of a VSS snapshot as part of the VSS api.
type VssSnapshotProperties struct {
snapshotID ole.GUID
snapshotSetID ole.GUID
snapshotsCount uint32
snapshotDeviceObject *uint16
originalVolumeName *uint16
originatingMachine *uint16
serviceMachine *uint16
exposedName *uint16
exposedPath *uint16
providerID ole.GUID
snapshotAttributes uint32
creationTimestamp uint64
status uint
}
// GetSnapshotDeviceObject returns root path to access the snapshot files
// and folders.
func (p *VssSnapshotProperties) GetSnapshotDeviceObject() string {
return ole.UTF16PtrToString(p.snapshotDeviceObject)
}
// UIID_IVSS_ASYNC defines to GUID of IVSSAsync.
var UIID_IVSS_ASYNC = ole.NewGUID("{507C37B4-CF5B-4e95-B0AF-14EB9767467E}")
// IVSSAsync VSS api interface.
type IVSSAsync struct {
ole.IUnknown
}
// IVSSAsyncVTable is the vtable for IVSSAsync.
type IVSSAsyncVTable struct {
ole.IUnknownVtbl
cancel uintptr
wait uintptr
queryStatus uintptr
}
// Constants for IVSSAsync api.
const (
VSS_S_ASYNC_PENDING = 0x00042309
VSS_S_ASYNC_FINISHED = 0x0004230A
VSS_S_ASYNC_CANCELLED = 0x0004230B
)
// getVTable returns the vtable for IVSSAsync.
func (vssAsync *IVSSAsync) getVTable() *IVSSAsyncVTable {
return (*IVSSAsyncVTable)(unsafe.Pointer(vssAsync.RawVTable))
}
// Cancel calls the equivalent VSS api.
func (vssAsync *IVSSAsync) Cancel() HRESULT {
result, _, _ := syscall.Syscall(vssAsync.getVTable().cancel, 1,
uintptr(unsafe.Pointer(vssAsync)), 0, 0)
return HRESULT(result)
}
// Wait calls the equivalent VSS api.
func (vssAsync *IVSSAsync) Wait(millis uint32) HRESULT {
result, _, _ := syscall.Syscall(vssAsync.getVTable().wait, 2, uintptr(unsafe.Pointer(vssAsync)),
uintptr(millis), 0)
return HRESULT(result)
}
// QueryStatus calls the equivalent VSS api.
func (vssAsync *IVSSAsync) QueryStatus() (HRESULT, uint32) {
var state uint32 = 0
result, _, _ := syscall.Syscall(vssAsync.getVTable().queryStatus, 3,
uintptr(unsafe.Pointer(vssAsync)), uintptr(unsafe.Pointer(&state)), 0)
return HRESULT(result), state
}
// WaitUntilAsyncFinished waits until either the async call is finshed or
// the given timeout is reached.
func (vssAsync *IVSSAsync) WaitUntilAsyncFinished(millis uint32) error {
hresult := vssAsync.Wait(millis)
err := newVssErrorIfResultNotOK("Wait() failed", hresult)
if err != nil {
vssAsync.Cancel()
return err
}
hresult, state := vssAsync.QueryStatus()
err = newVssErrorIfResultNotOK("QueryStatus() failed", hresult)
if err != nil {
vssAsync.Cancel()
return err
}
if state == VSS_S_ASYNC_CANCELLED {
return newVssTextError("async operation cancelled")
}
if state == VSS_S_ASYNC_PENDING {
vssAsync.Cancel()
return newVssTextError("async operation pending")
}
if state != VSS_S_ASYNC_FINISHED {
err = newVssErrorIfResultNotOK("async operation failed", HRESULT(state))
if err != nil {
return err
}
}
return nil
}
// MountPoint wraps all information of a snapshot of a mountpoint on a volume.
type MountPoint struct {
isSnapshotted bool
snapshotSetID ole.GUID
snapshotProperties VssSnapshotProperties
snapshotDeviceObject string
}
// IsSnapshotted is true if this mount point was snapshotted successfully.
func (p *MountPoint) IsSnapshotted() bool {
return p.isSnapshotted
}
// GetSnapshotDeviceObject returns root path to access the snapshot files and folders.
func (p *MountPoint) GetSnapshotDeviceObject() string {
return p.snapshotDeviceObject
}
// VssSnapshot wraps windows volume shadow copy api (vss) via a simple
// interface to create and delete a vss snapshot.
type VssSnapshot struct {
iVssBackupComponents *IVssBackupComponents
snapshotID ole.GUID
snapshotProperties VssSnapshotProperties
snapshotDeviceObject string
mountPointInfo map[string]MountPoint
timeoutInMillis uint32
}
// GetSnapshotDeviceObject returns root path to access the snapshot files
// and folders.
func (p *VssSnapshot) GetSnapshotDeviceObject() string {
return p.snapshotDeviceObject
}
// initializeCOMInterface initialize an instance of the VSS COM api
func initializeVssCOMInterface() (*ole.IUnknown, uintptr, error) {
vssInstance, err := loadIVssBackupComponentsConstructor()
if err != nil {
return nil, 0, err
}
// ensure COM is initialized before use
ole.CoInitializeEx(0, ole.COINIT_MULTITHREADED)
var oleIUnknown *ole.IUnknown
result, _, _ := vssInstance.Call(uintptr(unsafe.Pointer(&oleIUnknown)))
return oleIUnknown, result, nil
}
// HasSufficientPrivilegesForVSS returns true if the user is allowed to use VSS.
func HasSufficientPrivilegesForVSS() bool {
oleIUnknown, result, err := initializeVssCOMInterface()
if oleIUnknown != nil {
oleIUnknown.Release()
}
if err != nil {
return false
}
return !(HRESULT(result) == E_ACCESSDENIED)
}
// NewVssSnapshot creates a new vss snapshot. If creating the snapshots doesn't
// finish within the timeout an error is returned.
func NewVssSnapshot(
volume string, timeoutInSeconds uint, msgError ErrorHandler) (VssSnapshot, error) {
is64Bit, err := isRunningOn64BitWindows()
if err != nil {
return VssSnapshot{}, newVssTextError(fmt.Sprintf(
"Failed to detect windows architecture: %s", err.Error()))
}
if (is64Bit && runtime.GOARCH != "amd64") || (!is64Bit && runtime.GOARCH != "386") {
return VssSnapshot{}, newVssTextError(fmt.Sprintf("executables compiled for %s can't use "+
"VSS on other architectures. Please use an executable compiled for your platform.",
runtime.GOARCH))
}
timeoutInMillis := uint32(timeoutInSeconds * 1000)
oleIUnknown, result, err := initializeVssCOMInterface()
if err != nil {
if oleIUnknown != nil {
oleIUnknown.Release()
}
return VssSnapshot{}, err
}
defer oleIUnknown.Release()
switch HRESULT(result) {
case S_OK:
case E_ACCESSDENIED:
return VssSnapshot{}, newVssTextError(fmt.Sprintf("%s (%#x) The caller does not have "+
"sufficient backup privileges or is not an administrator.", HRESULT(result).Str(),
result))
default:
return VssSnapshot{}, newVssTextError(fmt.Sprintf("Failed to create VSS instance: %s (%#x)",
HRESULT(result).Str(), result))
}
comInterface, err := queryInterface(oleIUnknown, UUID_IVSS)
if err != nil {
return VssSnapshot{}, err
}
/*
https://microsoft.public.win32.programmer.kernel.narkive.com/aObDj2dD/volume-shadow-copy-backupcomplete-and-vss-e-bad-state
CreateVSSBackupComponents();
InitializeForBackup();
SetBackupState();
GatherWriterMetadata();
StartSnapshotSet();
AddToSnapshotSet();
PrepareForBackup();
DoSnapshotSet();
GetSnapshotProperties();
<Backup all files>
VssFreeSnapshotProperties();
BackupComplete();
*/
iVssBackupComponents := (*IVssBackupComponents)(unsafe.Pointer(comInterface))
if err := iVssBackupComponents.InitializeForBackup(); err != nil {
iVssBackupComponents.Release()
return VssSnapshot{}, err
}
if err := iVssBackupComponents.SetContext(VSS_CTX_BACKUP); err != nil {
iVssBackupComponents.Release()
return VssSnapshot{}, err
}
// see https://techcommunity.microsoft.com/t5/Storage-at-Microsoft/What-is-the-difference-between-VSS-Full-Backup-and-VSS-Copy/ba-p/423575
if err := iVssBackupComponents.SetBackupState(false, false, VSS_BT_COPY, false); err != nil {
iVssBackupComponents.Release()
return VssSnapshot{}, err
}
err = callAsyncFunctionAndWait(iVssBackupComponents.GatherWriterMetadata,
"GatherWriterMetadata", timeoutInMillis)
if err != nil {
iVssBackupComponents.Release()
return VssSnapshot{}, err
}
if isSupported, err := iVssBackupComponents.IsVolumeSupported(volume); err != nil {
iVssBackupComponents.Release()
return VssSnapshot{}, err
} else if !isSupported {
iVssBackupComponents.Release()
return VssSnapshot{}, newVssTextError(fmt.Sprintf("Snapshots are not supported for volume "+
"%s", volume))
}
snapshotSetID, err := iVssBackupComponents.StartSnapshotSet()
if err != nil {
iVssBackupComponents.Release()
return VssSnapshot{}, err
}
if err := iVssBackupComponents.AddToSnapshotSet(volume, &snapshotSetID); err != nil {
iVssBackupComponents.Release()
return VssSnapshot{}, err
}
mountPoints, err := enumerateMountedFolders(volume)
if err != nil {
iVssBackupComponents.Release()
return VssSnapshot{}, newVssTextError(fmt.Sprintf(
"failed to enumerate mount points for volume %s: %s", volume, err))
}
mountPointInfo := make(map[string]MountPoint)
for _, mountPoint := range mountPoints {
// ensure every mountpoint is available even without a valid
// snapshot because we need to consider this when backing up files
mountPointInfo[mountPoint] = MountPoint{isSnapshotted: false}
if isSupported, err := iVssBackupComponents.IsVolumeSupported(mountPoint); err != nil {
continue
} else if !isSupported {
continue
}
var mountPointSnapshotSetID ole.GUID
err := iVssBackupComponents.AddToSnapshotSet(mountPoint, &mountPointSnapshotSetID)
if err != nil {
iVssBackupComponents.Release()
return VssSnapshot{}, err
}
mountPointInfo[mountPoint] = MountPoint{isSnapshotted: true,
snapshotSetID: mountPointSnapshotSetID}
}
err = callAsyncFunctionAndWait(iVssBackupComponents.PrepareForBackup, "PrepareForBackup",
timeoutInMillis)
if err != nil {
// After calling PrepareForBackup one needs to call AbortBackup() before releasing the VSS
// instance for proper cleanup.
// It is not neccessary to call BackupComplete before releasing the VSS instance afterwards.
iVssBackupComponents.AbortBackup()
iVssBackupComponents.Release()
return VssSnapshot{}, err
}
err = callAsyncFunctionAndWait(iVssBackupComponents.DoSnapshotSet, "DoSnapshotSet",
timeoutInMillis)
if err != nil {
iVssBackupComponents.AbortBackup()
iVssBackupComponents.Release()
return VssSnapshot{}, err
}
var snapshotProperties VssSnapshotProperties
err = iVssBackupComponents.GetSnapshotProperties(snapshotSetID, &snapshotProperties)
if err != nil {
iVssBackupComponents.AbortBackup()
iVssBackupComponents.Release()
return VssSnapshot{}, err
}
for mountPoint, info := range mountPointInfo {
if !info.isSnapshotted {
continue
}
err := iVssBackupComponents.GetSnapshotProperties(info.snapshotSetID,
&info.snapshotProperties)
if err != nil {
msgError(mountPoint, errors.Errorf(
"VSS error: GetSnapshotProperties() for mount point %s returned error: ",
mountPoint, err))
info.isSnapshotted = false
} else {
info.snapshotDeviceObject = info.snapshotProperties.GetSnapshotDeviceObject()
}
mountPointInfo[mountPoint] = info
}
return VssSnapshot{iVssBackupComponents, snapshotSetID, snapshotProperties,
snapshotProperties.GetSnapshotDeviceObject(), mountPointInfo, timeoutInMillis}, nil
}
// Delete deletes the created snapshot.
func (p *VssSnapshot) Delete() error {
var err error
if err = vssFreeSnapshotProperties(&p.snapshotProperties); err != nil {
return err
}
for _, mountPoint := range p.mountPointInfo {
if mountPoint.isSnapshotted {
if err = vssFreeSnapshotProperties(&mountPoint.snapshotProperties); err != nil {
return err
}
}
}
if p.iVssBackupComponents != nil {
defer p.iVssBackupComponents.Release()
err = callAsyncFunctionAndWait(p.iVssBackupComponents.BackupComplete, "BackupComplete",
p.timeoutInMillis)
if err != nil {
return err
}
if _, _, e := p.iVssBackupComponents.DeleteSnapshots(p.snapshotID); e != nil {
err = newVssTextError(fmt.Sprintf("Failed to delete snapshot: %s", e.Error()))
p.iVssBackupComponents.AbortBackup()
if err != nil {
return err
}
}
}
return nil
}
// asyncCallFunc is the callback type for callAsyncFunctionAndWait.
type asyncCallFunc func() (*IVSSAsync, error)
// callAsyncFunctionAndWait calls an async functions and waits for it to either
// finish or timeout.
func callAsyncFunctionAndWait(function asyncCallFunc, name string, timeoutInMillis uint32) error {
iVssAsync, err := function()
if err != nil {
return err
}
if iVssAsync == nil {
return newVssTextError(fmt.Sprintf("%s() returned nil", name))
}
err = iVssAsync.WaitUntilAsyncFinished(timeoutInMillis)
iVssAsync.Release()
return err
}
// loadIVssBackupComponentsConstructor finds the constructor of the VSS api
// inside the VSS dynamic library.
func loadIVssBackupComponentsConstructor() (*windows.LazyProc, error) {
createInstanceName := "?CreateVssBackupComponents@@YAJPEAPEAVIVssBackupComponents@@@Z"
if runtime.GOARCH == "386" {
createInstanceName = "?CreateVssBackupComponents@@YGJPAPAVIVssBackupComponents@@@Z"
}
return findVssProc(createInstanceName)
}
// findVssProc find a function with the given name inside the VSS api
// dynamic library.
func findVssProc(procName string) (*windows.LazyProc, error) {
vssDll := windows.NewLazySystemDLL("VssApi.dll")
err := vssDll.Load()
if err != nil {
return &windows.LazyProc{}, err
}
proc := vssDll.NewProc(procName)
err = proc.Find()
if err != nil {
return &windows.LazyProc{}, err
}
return proc, nil
}
// queryInterface is a wrapper around the windows QueryInterface api.
func queryInterface(oleIUnknown *ole.IUnknown, guid *ole.GUID) (*interface{}, error) {
var ivss *interface{}
result, _, _ := syscall.Syscall(oleIUnknown.VTable().QueryInterface, 3,
uintptr(unsafe.Pointer(oleIUnknown)), uintptr(unsafe.Pointer(guid)),
uintptr(unsafe.Pointer(&ivss)))
if result != 0 {
return nil, newVssError("QueryInterface failed", HRESULT(result))
}
return ivss, nil
}
// isRunningOn64BitWindows returns true if running on 64-bit windows.
func isRunningOn64BitWindows() (bool, error) {
if runtime.GOARCH == "amd64" {
return true, nil
}
isWow64 := false
err := windows.IsWow64Process(windows.CurrentProcess(), &isWow64)
if err != nil {
return false, err
}
return isWow64, nil
}
// enumerateMountedFolders returns all mountpoints on the given volume.
func enumerateMountedFolders(volume string) ([]string, error) {
var mountedFolders []string
volumeNamePointer, err := syscall.UTF16PtrFromString(volume)
if err != nil {
return mountedFolders, err
}
volumeMountPointBuffer := make([]uint16, windows.MAX_LONG_PATH)
handle, err := windows.FindFirstVolumeMountPoint(volumeNamePointer, &volumeMountPointBuffer[0],
windows.MAX_LONG_PATH)
if err != nil {
// if there are no volumes an error is returned
return mountedFolders, nil
}
defer windows.FindVolumeMountPointClose(handle)
volumeMountPoint := syscall.UTF16ToString(volumeMountPointBuffer)
mountedFolders = append(mountedFolders, cleanupVolumeMountPoint(volume, volumeMountPoint))
for {
err = windows.FindNextVolumeMountPoint(handle, &volumeMountPointBuffer[0],
windows.MAX_LONG_PATH)
if err != nil {
if err == syscall.ERROR_NO_MORE_FILES {
break
}
return mountedFolders,
newVssTextError("FindNextVolumeMountPoint() failed: " + err.Error())
}
volumeMountPoint := syscall.UTF16ToString(volumeMountPointBuffer)
mountedFolders = append(mountedFolders, cleanupVolumeMountPoint(volume, volumeMountPoint))
}
return mountedFolders, nil
}
func cleanupVolumeMountPoint(volume, mountPoint string) string {
return strings.ToLower(filepath.Join(volume, mountPoint) + string(filepath.Separator))
}