mirror of
https://github.com/restic/restic.git
synced 2026-07-18 13:14:54 +00:00
Merge pull request #5112 from MichaelEischer/fix-vss-root-volume
Fix VSS metadata error (master)
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user