diff --git a/cmd/tailscale/depaware.txt b/cmd/tailscale/depaware.txt index ef1fac7d9..bcc0ccdb7 100644 --- a/cmd/tailscale/depaware.txt +++ b/cmd/tailscale/depaware.txt @@ -89,7 +89,6 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep tailscale.com/util/groupmember from tailscale.com/cmd/tailscale/cli tailscale.com/util/lineread from tailscale.com/net/interfaces+ W tailscale.com/util/winutil from tailscale.com/hostinfo - W 💣 tailscale.com/util/winutil/vss from tailscale.com/util/winutil tailscale.com/version from tailscale.com/cmd/tailscale/cli+ tailscale.com/version/distro from tailscale.com/cmd/tailscale/cli+ tailscale.com/wgengine/filter from tailscale.com/types/netmap diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index 97c4fd28e..9baa40f34 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -274,7 +274,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de tailscale.com/util/systemd from tailscale.com/control/controlclient+ tailscale.com/util/uniq from tailscale.com/wgengine/magicsock tailscale.com/util/winutil from tailscale.com/cmd/tailscaled+ - W 💣 tailscale.com/util/winutil/vss from tailscale.com/util/winutil tailscale.com/version from tailscale.com/cmd/tailscaled+ tailscale.com/version/distro from tailscale.com/cmd/tailscaled+ W tailscale.com/wf from tailscale.com/cmd/tailscaled diff --git a/util/winutil/vss/vss_windows.go b/util/winutil/vss/vss_windows.go deleted file mode 100644 index a0baf2b36..000000000 --- a/util/winutil/vss/vss_windows.go +++ /dev/null @@ -1,359 +0,0 @@ -// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package vss provides a minimal set of wrappers for the COM interfaces used for -// accessing Windows's Volume Shadow Copy Service. -package vss - -import ( - "errors" - "fmt" - "io" - "sort" - "syscall" - "unsafe" - - "golang.org/x/sys/windows" -) - -// Type representing a C pointer to a null-terminated UTF-16 string that was allocated -// by the COM runtime. -type COMAllocatedString uintptr -type VSS_TIMESTAMP int64 - -// SnapshotProperties is the Go representation of the VSS_SNAPSHOT_PROP structure from the Windows SDK -type SnapshotProperties struct { - SnapshotId windows.GUID - SnapshotSetId windows.GUID - SnapshotsCount int32 - SnapshotDeviceObject COMAllocatedString - OriginalVolumeName COMAllocatedString - OriginatingMachine COMAllocatedString - ServiceMachine COMAllocatedString - ExposedName COMAllocatedString - ExposedPath COMAllocatedString - ProviderId windows.GUID - SnapshotAttributes int32 - CreationTimestamp VSS_TIMESTAMP - Status int32 -} - -// Because of the constraints that this package applies to queries, the objType -// field may be ignored when reading its data. -type ObjectProperties struct { - objType int32 - Obj SnapshotProperties -} - -type SnapshotList []ObjectProperties - -// SnapshotEnumerator is the interface that enables execution of a VSS query. -// QuerySnapshots returns a SnapshotList of all available snapshots. -// The elements of the SnapshotList should be returned in reverse chronological order. -type SnapshotEnumerator interface { - io.Closer - QuerySnapshots() (SnapshotList, error) -} - -var ( - vssApi = windows.NewLazySystemDLL("VssApi.dll") - procCreateVssBackupComponentsInternal = vssApi.NewProc("CreateVssBackupComponentsInternal") -) - -const vssCtxClientAccessibleWriters = 0x0000000d - -// NewSnapshotEnumerator instantiates the necessary OS facilities for accessing -// the Volume Shadow Copy service, and then returns a SnapshotEnumerator that -// may then be used for executing a query against the service. -func NewSnapshotEnumerator() (SnapshotEnumerator, error) { - var result vssBackupComponentsWrap - hresult, _, _ := procCreateVssBackupComponentsInternal.Call(uintptr(unsafe.Pointer(&result.iface))) - err := errorFromHRESULT(hresult) - if err != nil { - return nil, err - } - - defer func() { - if err != nil { - result.Close() - } - }() - - err = result.iface.InitializeForBackup() - if err != nil { - return nil, err - } - - // vssCtxClientAccessibleWriters is the context that we need to be able to access - // system restore points. - err = result.iface.SetContext(vssCtxClientAccessibleWriters) - if err != nil { - return nil, err - } - - return &result, nil -} - -func (s *COMAllocatedString) Close() error { - if s != nil { - windows.CoTaskMemFree(unsafe.Pointer(*s)) - *s = 0 - } - - return nil -} - -func (s *COMAllocatedString) String() string { - if s == nil { - return "" - } - - return windows.UTF16PtrToString((*uint16)(unsafe.Pointer(*s))) -} - -func (ts VSS_TIMESTAMP) ToFiletime() windows.Filetime { - return *((*windows.Filetime)(unsafe.Pointer(&ts))) -} - -// Converts a windows.Filetime to a VSS_TIMESTAMP -func VSSTimestampFromFiletime(ft windows.Filetime) VSS_TIMESTAMP { - return *((*VSS_TIMESTAMP)(unsafe.Pointer(&ft))) -} - -func (p *SnapshotProperties) Close() error { - if p == nil { - return nil - } - - p.SnapshotDeviceObject.Close() - p.OriginalVolumeName.Close() - p.OriginatingMachine.Close() - p.ServiceMachine.Close() - p.ExposedName.Close() - p.ExposedPath.Close() - return nil -} - -func (props *ObjectProperties) Close() error { - if props == nil { - return nil - } - - return props.Obj.Close() -} - -func (snapList *SnapshotList) Close() error { - if snapList == nil { - return nil - } - - for _, snap := range *snapList { - snap.Close() - } - - return nil -} - -func errorFromHRESULT(value uintptr) error { - // In C, HRESULTS are typedef'd as LONG, which on Windows is always int32 - hr := int32(value) - if hr < 0 { - return windows.Errno(hr) - } - - return nil -} - -type unknownVtbl struct { - QueryInterface uintptr - AddRef uintptr - Release uintptr -} - -// The complete vtable for IVssEnumObject. -// We only call Release and Next, so most of these fields, while populated by -// Windows, are unused by us. -type vssEnumObjectVtbl struct { - unknownVtbl - Next uintptr - Skip uintptr - Reset uintptr - Clone uintptr -} - -type vssEnumObjectABI struct { - vtbl *vssEnumObjectVtbl -} - -func (iface *vssEnumObjectABI) Release() int32 { - result, _, _ := syscall.Syscall(iface.vtbl.Release, 1, uintptr(unsafe.Pointer(iface)), 0, 0) - return int32(result) -} - -func (iface *vssEnumObjectABI) Next() ([]ObjectProperties, error) { - var props [16]ObjectProperties - var numFetched uint32 - hresult, _, _ := syscall.Syscall6(iface.vtbl.Next, 4, uintptr(unsafe.Pointer(iface)), - uintptr(len(props)), uintptr(unsafe.Pointer(&props[0])), uintptr(unsafe.Pointer(&numFetched)), 0, 0) - - err := errorFromHRESULT(hresult) - if err != nil { - return nil, err - } - - // For some reason x/sys/windows gives HRESULT error codes a type of - // windows.Handle, which is wrong, so we're being explicit here. - if int32(hresult) == int32(windows.S_FALSE) { - err = io.EOF - } - - return props[:numFetched], err -} - -// The complete vtable for IVssBackupComponents. -// We only call Release, InitializeForBackup, SetContext, and Query, -// so most of these fields, while populated by Windows, are unused by us. -type vssBackupComponentsVtbl struct { - unknownVtbl - 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 -} - -type vssBackupComponentsABI struct { - vtbl *vssBackupComponentsVtbl -} - -func (iface *vssBackupComponentsABI) Release() int32 { - result, _, _ := syscall.Syscall(iface.vtbl.Release, 1, uintptr(unsafe.Pointer(iface)), 0, 0) - return int32(result) -} - -func (iface *vssBackupComponentsABI) InitializeForBackup() error { - // Note that we pass a second argument that is a null C pointer, i.e. 0 - hresult, _, _ := syscall.Syscall(iface.vtbl.InitializeForBackup, 2, uintptr(unsafe.Pointer(iface)), 0, 0) - return errorFromHRESULT(hresult) -} - -func (iface *vssBackupComponentsABI) SetContext(context int32) error { - hresult, _, _ := syscall.Syscall(iface.vtbl.SetContext, 2, uintptr(unsafe.Pointer(iface)), uintptr(context), 0) - return errorFromHRESULT(hresult) -} - -const ( - vssObjectUnknown = 0 - vssObjectNone = 1 - vssObjectSnapshotSet = 2 - vssObjectSnapshot = 3 - vssObjectProvider = 4 -) - -// QuerySnapshots returns the list of applicable snapshots as a SnapshotList -// (as opposed to a channel-based implementation) because we need to be able to -// access the snapshot information in reverse-chronological order. -func (iface *vssBackupComponentsABI) QuerySnapshots() (SnapshotList, error) { - // Perform the Query. If successful, the query will produce an enumeration object. - var GUID_NULL windows.GUID - var enumObj *vssEnumObjectABI - hresult, _, _ := syscall.Syscall6(iface.vtbl.Query, 5, uintptr(unsafe.Pointer(iface)), uintptr(unsafe.Pointer(&GUID_NULL)), - vssObjectNone, vssObjectSnapshot, uintptr(unsafe.Pointer(&enumObj)), 0) - err := errorFromHRESULT(hresult) - if err != nil { - return nil, err - } - defer enumObj.Release() - - // Build up the complete list of snapshots from the enumerator object. - var result SnapshotList - chunk, err := enumObj.Next() - for ok := err == nil || errors.Is(err, io.EOF); ok; ok = err == nil { - if result == nil { - result = chunk - } else { - for _, item := range chunk { - result = append(result, item) - } - } - - chunk, err = enumObj.Next() - } - - if err != nil && !errors.Is(err, io.EOF) { - return nil, err - } - - // Sort in reverse chronological order so we may easily iterate from newest to oldest - sort.Slice(result, func(i, j int) bool { - return result[i].Obj.CreationTimestamp > result[j].Obj.CreationTimestamp - }) - - return result, nil -} - -type vssBackupComponentsWrap struct { - iface *vssBackupComponentsABI -} - -func (vss *vssBackupComponentsWrap) Close() error { - if vss == nil || vss.iface == nil { - return nil - } - - vss.iface.Release() - vss.iface = nil - return nil -} - -func (vss *vssBackupComponentsWrap) QuerySnapshots() (SnapshotList, error) { - if vss == nil { - return nil, fmt.Errorf("Called QuerySnapshots on a nil vssBackupComponentsWrap") - } - - return vss.iface.QuerySnapshots() -} diff --git a/util/winutil/winrestore_windows.go b/util/winutil/winrestore_windows.go deleted file mode 100644 index 0c2ba4042..000000000 --- a/util/winutil/winrestore_windows.go +++ /dev/null @@ -1,210 +0,0 @@ -// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package winutil - -import ( - "errors" - "fmt" - "os" - "path/filepath" - "runtime" - "strings" - - "golang.org/x/sys/windows" - - "tailscale.com/paths" - "tailscale.com/util/winutil/vss" -) - -// StopWalking is the error value that a WalkSnapshotsFunc should return when -// it successfully completes and no longer needs to examine any more snapshots. -var StopWalking error = errors.New("Stop walking") - -// WalkSnapshotsFunc is the type of the function called by WalkSnapshotsForLegacyStateDir -// to visit each mapped VSS snapshot. -// The path argument is the path of the directory containing the Tailscale state. -// The props argument contains the snapshot properties of the current snapshot, and -// should be treated as read-only. -// The function may return StopWalking if further walking is no longer necessary. -// Otherwise it should return nil to proceed with the walk, or an error. -type WalkSnapshotsFunc func(path string, props vss.SnapshotProperties) error - -// WalkSnapshotsForLegacyStateDir enumerates available snapshots from the -// Volume Shadow Copy service. For each snapshot originating from this computer's -// C: volume, the snapshot is mounted to a temporary location inside the -// Tailscaled state directory. -// If the mounted snapshot contains a path to a legacy state directory (located under -// C:\Windows\System32\config\systemprofile\AppData\Local), the fn argument is -// invoked with the fully-qualified path to the mounted state directory, as well -// as the properties of the snapshot itself. -// A mounted snapshot that does not contain a path to a legacy state directory is -// not considered to be an error, the snapshot is ignored, and the walk continues. -// If fn returns StopWalking, then the walk is terminated but is considered to -// have been successful and nil is returned. -// If fn returns a different error, then the walk is terminated and fn's error -// is wrapped and then returned to the caller. -func WalkSnapshotsForLegacyStateDir(fn WalkSnapshotsFunc) error { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - - // Ideally COM would be initialized process-wide, but until we have that - // conversation this should be okay, especially given that this function will - // only be called when a migration is necessary. - err := windows.CoInitializeEx(0, windows.COINIT_MULTITHREADED) - if err != nil { - return err - } - defer windows.CoUninitialize() - - sysVol, err := getSystemVolumeName() - if err != nil { - return err - } - - thisMachine, err := getFullyQualifiedComputerName() - if err != nil { - return err - } - - // We'll map each snapshot to a subdir inside our tailscaled state dir - mountPt := filepath.Dir(paths.DefaultTailscaledStateFile()) - - vssSnapshotEnumerator, err := vss.NewSnapshotEnumerator() - if err != nil { - return err - } - defer vssSnapshotEnumerator.Close() - - snapshots, err := vssSnapshotEnumerator.QuerySnapshots() - if err != nil { - return err - } - defer snapshots.Close() - - for _, snap := range snapshots { - if !strings.EqualFold(snap.Obj.OriginalVolumeName.String(), sysVol) || - !strings.EqualFold(snap.Obj.OriginatingMachine.String(), thisMachine) { - // These snapshots do not belong to our computer's C: volume, so we should skip them. - continue - } - - mounted, err := mountSnapshotDevice(snap.Obj, mountPt) - if err != nil { - return fmt.Errorf("Mapping snapshot device %v: %w", snap.Obj.SnapshotDeviceObject.String(), err) - } - defer mounted.Close() - - legacyStateDir, err := mounted.findLegacyStateDir() - if err != nil { - // Not all snapshots will necessarily contain the state dir, so this is not fatal - continue - } - - err = fn(legacyStateDir, snap.Obj) - if errors.Is(err, StopWalking) { - return nil - } - if err != nil { - return fmt.Errorf("WalkSnapshotsFunc returned error %w", err) - } - } - - return nil -} - -func getSystemVolumeName() (string, error) { - // This is the exact length of a volume name, including nul terminator (per MSDN) - var volName [50]uint16 - - // Modern Windows always requires that the OS be installed on C: - mountPt, err := windows.UTF16PtrFromString("C:\\") - if err != nil { - return "", err - } - - err = windows.GetVolumeNameForVolumeMountPoint(mountPt, &volName[0], uint32(len(volName))) - if err != nil { - return "", err - } - - return windows.UTF16ToString(volName[:len(volName)-1]), nil -} - -type mountedSnapshot string - -func (snap *mountedSnapshot) Close() error { - os.Remove(string(*snap)) - *snap = "" - return nil -} - -func mountSnapshotDevice(snap vss.SnapshotProperties, mountPath string) (mountedSnapshot, error) { - fi, err := os.Stat(mountPath) - if err != nil { - return "", err - } - if !fi.IsDir() { - return "", os.ErrInvalid - } - - devPath := snap.SnapshotDeviceObject.String() - linkPath := filepath.Join(mountPath, filepath.Base(devPath)) - - linkPathUTF16, err := windows.UTF16PtrFromString(linkPath) - if err != nil { - return "", err - } - - // The target needs to end with a backslash or else the symlink won't resolve correctly - deviceUTF16, err := windows.UTF16PtrFromString(devPath + "\\") - if err != nil { - return "", err - } - - err = windows.CreateSymbolicLink(linkPathUTF16, deviceUTF16, windows.SYMBOLIC_LINK_FLAG_DIRECTORY) - if err != nil { - return "", err - } - - return mountedSnapshot(linkPath), nil -} - -func (snap *mountedSnapshot) findLegacyStateDir() (string, error) { - legacyStateDir := filepath.Dir(paths.LegacyStateFilePath()) - relPath, err := filepath.Rel("C:\\", legacyStateDir) - if err != nil { - return "", err - } - - snapStateDir := filepath.Join(string(*snap), relPath) - fi, err := os.Stat(snapStateDir) - if err != nil { - return "", err - } - if !fi.IsDir() { - return "", os.ErrInvalid - } - - return snapStateDir, nil -} - -func getFullyQualifiedComputerName() (string, error) { - var desiredLen uint32 - err := windows.GetComputerNameEx(windows.ComputerNamePhysicalDnsFullyQualified, nil, &desiredLen) - if !errors.Is(err, windows.ERROR_MORE_DATA) { - return "", err - } - - buf := make([]uint16, desiredLen+1) - - // Note: bufLen includes nul terminator on input, but excludes nul terminator as output - bufLen := uint32(len(buf)) - err = windows.GetComputerNameEx(windows.ComputerNamePhysicalDnsFullyQualified, &buf[0], &bufLen) - if err != nil { - return "", err - } - - return windows.UTF16ToString(buf[:bufLen]), nil -}