Merge pull request #5112 from MichaelEischer/fix-vss-root-volume

Fix VSS metadata error (master)
This commit is contained in:
Michael Eischer
2024-11-03 21:30:39 +01:00
committed by GitHub
11 changed files with 145 additions and 87 deletions
-18
View File
@@ -8,7 +8,6 @@ import (
"encoding/binary"
"errors"
"fmt"
"strings"
"syscall"
"unsafe"
@@ -299,20 +298,3 @@ func pathSupportsExtendedAttributes(path string) (supported bool, err error) {
supported = (fileSystemFlags & windows.FILE_SUPPORTS_EXTENDED_ATTRIBUTES) != 0
return supported, nil
}
// getVolumePathName returns the volume path name for the given path.
func getVolumePathName(path string) (volumeName string, err error) {
utf16Path, err := windows.UTF16PtrFromString(path)
if err != nil {
return "", err
}
// Get the volume path (e.g., "D:")
var volumePath [windows.MAX_PATH + 1]uint16
err = windows.GetVolumePathName(utf16Path, &volumePath[0], windows.MAX_PATH+1)
if err != nil {
return "", err
}
// Trim any trailing backslashes
volumeName = strings.TrimRight(windows.UTF16ToString(volumePath[:]), "\\")
return volumeName, nil
}
-44
View File
@@ -10,7 +10,6 @@ import (
"os"
"path/filepath"
"reflect"
"strings"
"syscall"
"testing"
"unsafe"
@@ -278,46 +277,3 @@ func TestPathSupportsExtendedAttributes(t *testing.T) {
t.Error("Expected an error for non-existent path, but got nil")
}
}
func TestGetVolumePathName(t *testing.T) {
tempDirVolume := filepath.VolumeName(os.TempDir())
testCases := []struct {
name string
path string
expectedPrefix string
}{
{
name: "Root directory",
path: os.Getenv("SystemDrive") + `\`,
expectedPrefix: os.Getenv("SystemDrive"),
},
{
name: "Nested directory",
path: os.Getenv("SystemDrive") + `\Windows\System32`,
expectedPrefix: os.Getenv("SystemDrive"),
},
{
name: "Temp directory",
path: os.TempDir() + `\`,
expectedPrefix: tempDirVolume,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
volumeName, err := getVolumePathName(tc.path)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if !strings.HasPrefix(volumeName, tc.expectedPrefix) {
t.Errorf("Expected volume name to start with %s, but got %s", tc.expectedPrefix, volumeName)
}
})
}
// Test with an invalid path
_, err := getVolumePathName("Z:\\NonExistentPath")
if err == nil {
t.Error("Expected an error for non-existent path, but got nil")
}
}
+13 -4
View File
@@ -18,19 +18,28 @@ func fixpath(name string) string {
abspath, err := filepath.Abs(name)
if err == nil {
// Check if \\?\UNC\ already exist
if strings.HasPrefix(abspath, `\\?\UNC\`) {
if strings.HasPrefix(abspath, uncPathPrefix) {
return abspath
}
// Check if \\?\GLOBALROOT exists which marks volume shadow copy snapshots
if strings.HasPrefix(abspath, globalRootPrefix) {
if strings.Count(abspath, `\`) == 5 {
// Append slash if this just a volume name, e.g. `\\?\GLOBALROOT\Device\HarddiskVolumeShadowCopyXX`
// Without the trailing slash any access to the volume itself will fail.
return abspath + string(filepath.Separator)
}
return abspath
}
// Check if \\?\ already exist
if strings.HasPrefix(abspath, `\\?\`) {
if strings.HasPrefix(abspath, extendedPathPrefix) {
return abspath
}
// Check if path starts with \\
if strings.HasPrefix(abspath, `\\`) {
return strings.Replace(abspath, `\\`, `\\?\UNC\`, 1)
return strings.Replace(abspath, `\\`, uncPathPrefix, 1)
}
// Normal path
return `\\?\` + abspath
return extendedPathPrefix + abspath
}
return name
}
+1 -1
View File
@@ -176,7 +176,7 @@ func (fs *LocalVss) snapshotPath(path string) string {
return path
}
fixPath = strings.TrimPrefix(fixpath(path), `\\?\`)
fixPath = strings.TrimPrefix(fixPath, `\\?\`)
fixPathLower := strings.ToLower(fixPath)
volumeName := filepath.VolumeName(fixPath)
volumeNameLower := strings.ToLower(volumeName)
+35 -3
View File
@@ -325,8 +325,11 @@ func nodeFillGenericAttributes(node *restic.Node, path string, stat *ExtendedFil
return false, nil
}
if strings.HasSuffix(filepath.Clean(path), `\`) {
// filepath.Clean(path) ends with '\' for Windows root volume paths only
isVolume, err := isVolumePath(path)
if err != nil {
return false, err
}
if isVolume {
// Do not process file attributes, created time and sd for windows root volume paths
// Security descriptors are not supported for root volume paths.
// Though file attributes and created time are supported for root volume paths,
@@ -335,7 +338,7 @@ func nodeFillGenericAttributes(node *restic.Node, path string, stat *ExtendedFil
if err != nil {
return false, err
}
return allowExtended, nil
return allowExtended, err
}
var sd *[]byte
@@ -420,6 +423,35 @@ func checkAndStoreEASupport(path string) (isEASupportedVolume bool, err error) {
return isEASupportedVolume, err
}
// getVolumePathName returns the volume path name for the given path.
func getVolumePathName(path string) (volumeName string, err error) {
utf16Path, err := windows.UTF16PtrFromString(path)
if err != nil {
return "", err
}
// Get the volume path (e.g., "D:")
var volumePath [windows.MAX_PATH + 1]uint16
err = windows.GetVolumePathName(utf16Path, &volumePath[0], windows.MAX_PATH+1)
if err != nil {
return "", err
}
// Trim any trailing backslashes
volumeName = strings.TrimRight(windows.UTF16ToString(volumePath[:]), "\\")
return volumeName, nil
}
// isVolumePath returns whether a path refers to a volume
func isVolumePath(path string) (bool, error) {
volName, err := prepareVolumeName(path)
if err != nil {
return false, err
}
cleanPath := filepath.Clean(path)
cleanVolume := filepath.Clean(volName + `\`)
return cleanPath == cleanVolume, nil
}
// prepareVolumeName prepares the volume name for different cases in Windows
func prepareVolumeName(path string) (volumeName string, err error) {
// Check if it's an extended length path
+52 -2
View File
@@ -451,10 +451,17 @@ func TestPrepareVolumeName(t *testing.T) {
expectError: false,
expectedEASupported: false,
},
{
name: "Volume Shadow Copy root",
path: `\\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy5555`,
expectedVolume: `\\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy5555`,
expectError: false,
expectedEASupported: false,
},
{
name: "Volume Shadow Copy path",
path: `\\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy1\Users\test`,
expectedVolume: `\\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy1`,
path: `\\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy5555\Users\test`,
expectedVolume: `\\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy5555`,
expectError: false,
expectedEASupported: false,
},
@@ -526,3 +533,46 @@ func getOSVolumeGUIDPath(t *testing.T) string {
return windows.UTF16ToString(volumeGUID[:])
}
func TestGetVolumePathName(t *testing.T) {
tempDirVolume := filepath.VolumeName(os.TempDir())
testCases := []struct {
name string
path string
expectedPrefix string
}{
{
name: "Root directory",
path: os.Getenv("SystemDrive") + `\`,
expectedPrefix: os.Getenv("SystemDrive"),
},
{
name: "Nested directory",
path: os.Getenv("SystemDrive") + `\Windows\System32`,
expectedPrefix: os.Getenv("SystemDrive"),
},
{
name: "Temp directory",
path: os.TempDir() + `\`,
expectedPrefix: tempDirVolume,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
volumeName, err := getVolumePathName(tc.path)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if !strings.HasPrefix(volumeName, tc.expectedPrefix) {
t.Errorf("Expected volume name to start with %s, but got %s", tc.expectedPrefix, volumeName)
}
})
}
// Test with an invalid path
_, err := getVolumePathName("Z:\\NonExistentPath")
if err == nil {
t.Error("Expected an error for non-existent path, but got nil")
}
}