forked from TrueCloudLab/restic
Fix extended attributes handling for VSS snapshots
This commit is contained in:
parent
910f64ce47
commit
1d392a36f9
2 changed files with 80 additions and 28 deletions
|
@ -8,6 +8,7 @@ import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
|
@ -298,3 +299,20 @@ func PathSupportsExtendedAttributes(path string) (supported bool, err error) {
|
||||||
supported = (fileSystemFlags & windows.FILE_SUPPORTS_EXTENDED_ATTRIBUTES) != 0
|
supported = (fileSystemFlags & windows.FILE_SUPPORTS_EXTENDED_ATTRIBUTES) != 0
|
||||||
return supported, nil
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -407,38 +407,72 @@ func (node *Node) fillGenericAttributes(path string, fi os.FileInfo, stat *statT
|
||||||
// checkAndStoreEASupport checks if the volume of the path supports extended attributes and stores the result in a map
|
// checkAndStoreEASupport checks if the volume of the path supports extended attributes and stores the result in a map
|
||||||
// If the result is already in the map, it returns the result from the map.
|
// If the result is already in the map, it returns the result from the map.
|
||||||
func checkAndStoreEASupport(path string) (isEASupportedVolume bool, err error) {
|
func checkAndStoreEASupport(path string) (isEASupportedVolume bool, err error) {
|
||||||
|
var volumeName string
|
||||||
|
volumeName, err = prepareVolumeName(path)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if volumeName != "" {
|
||||||
|
// First check if the manually prepared volume name is already in the map
|
||||||
|
eaSupportedValue, exists := eaSupportedVolumesMap.Load(volumeName)
|
||||||
|
if exists {
|
||||||
|
return eaSupportedValue.(bool), nil
|
||||||
|
}
|
||||||
|
// If not found, check if EA is supported with manually prepared volume name
|
||||||
|
isEASupportedVolume, err = fs.PathSupportsExtendedAttributes(volumeName + `\`)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If an entry is not found, get the actual volume name using the GetVolumePathName function
|
||||||
|
volumeNameActual, err := fs.GetVolumePathName(path)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if volumeNameActual != volumeName {
|
||||||
|
// If the actual volume name is different, check cache for the actual volume name
|
||||||
|
eaSupportedValue, exists := eaSupportedVolumesMap.Load(volumeNameActual)
|
||||||
|
if exists {
|
||||||
|
return eaSupportedValue.(bool), nil
|
||||||
|
}
|
||||||
|
// If the actual volume name is different and is not in the map, again check if the new volume supports extended attributes with the actual volume name
|
||||||
|
isEASupportedVolume, err = fs.PathSupportsExtendedAttributes(volumeNameActual + `\`)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
eaSupportedVolumesMap.Store(volumeNameActual, isEASupportedVolume)
|
||||||
|
return isEASupportedVolume, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
// Check if it's an extended length path
|
||||||
|
if strings.HasPrefix(path, globalRootPrefix) {
|
||||||
|
// Extract the VSS snapshot volume name eg. `\\?\GLOBALROOT\Device\HarddiskVolumeShadowCopyXX`
|
||||||
|
if parts := strings.SplitN(path, `\`, 7); len(parts) >= 6 {
|
||||||
|
volumeName = strings.Join(parts[:6], `\`)
|
||||||
|
} else {
|
||||||
|
volumeName = filepath.VolumeName(path)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
if strings.HasPrefix(path, uncPathPrefix) {
|
if strings.HasPrefix(path, uncPathPrefix) {
|
||||||
// Convert \\?\UNC\ extended path to standard path to get the volume name correctly
|
// Convert \\?\UNC\ extended path to standard path to get the volume name correctly
|
||||||
path = `\\` + path[len(uncPathPrefix):]
|
path = `\\` + path[len(uncPathPrefix):]
|
||||||
} else if strings.HasPrefix(path, extendedPathPrefix) {
|
} else if strings.HasPrefix(path, extendedPathPrefix) {
|
||||||
//Extended length path prefix needs to be trimmed to get the volume name correctly
|
//Extended length path prefix needs to be trimmed to get the volume name correctly
|
||||||
path = path[len(extendedPathPrefix):]
|
path = path[len(extendedPathPrefix):]
|
||||||
} else if strings.HasPrefix(path, globalRootPrefix) {
|
|
||||||
// EAs are not supported for \\?\GLOBALROOT i.e. VSS snapshots
|
|
||||||
return false, nil
|
|
||||||
} else {
|
} else {
|
||||||
// Use the absolute path
|
// Use the absolute path
|
||||||
path, err = filepath.Abs(path)
|
path, err = filepath.Abs(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Errorf("failed to get absolute path: %w", err)
|
return "", fmt.Errorf("failed to get absolute path: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
volumeName := filepath.VolumeName(path)
|
volumeName = filepath.VolumeName(path)
|
||||||
if volumeName == "" {
|
|
||||||
return false, nil
|
|
||||||
}
|
}
|
||||||
eaSupportedValue, exists := eaSupportedVolumesMap.Load(volumeName)
|
return volumeName, nil
|
||||||
if exists {
|
|
||||||
return eaSupportedValue.(bool), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add backslash to the volume name to ensure it is a valid path
|
|
||||||
isEASupportedVolume, err = fs.PathSupportsExtendedAttributes(volumeName + `\`)
|
|
||||||
if err == nil {
|
|
||||||
eaSupportedVolumesMap.Store(volumeName, isEASupportedVolume)
|
|
||||||
}
|
|
||||||
return isEASupportedVolume, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// windowsAttrsToGenericAttributes converts the WindowsAttributes to a generic attributes map using reflection
|
// windowsAttrsToGenericAttributes converts the WindowsAttributes to a generic attributes map using reflection
|
||||||
|
|
Loading…
Reference in a new issue