forked from TrueCloudLab/restic
Merge pull request #4980 from zmanda/unsupported-ea-handling
Skip ExtendedAttribute processing in Windows for volumes that do not support EA
This commit is contained in:
commit
86390b453d
4 changed files with 111 additions and 19 deletions
12
changelog/unreleased/pull-4980
Normal file
12
changelog/unreleased/pull-4980
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
Bugfix: Skip EA processing in Windows for volumes that do not support EA
|
||||||
|
|
||||||
|
Restic was failing to backup files on some windows paths like network
|
||||||
|
drives because of errors while fetching extended attributes.
|
||||||
|
Either they return error codes like windows.E_NOT_SET or
|
||||||
|
windows.ERROR_INVALID_FUNCTION or it results in slower backups.
|
||||||
|
Restic now completely skips the attempt to fetch extended attributes
|
||||||
|
for such volumes where it is not supported.
|
||||||
|
|
||||||
|
https://github.com/restic/restic/pull/4980
|
||||||
|
https://github.com/restic/restic/issues/4955
|
||||||
|
https://github.com/restic/restic/issues/4950
|
|
@ -283,3 +283,18 @@ func setFileEA(handle windows.Handle, iosb *ioStatusBlock, buf *uint8, bufLen ui
|
||||||
status = ntStatus(r0)
|
status = ntStatus(r0)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PathSupportsExtendedAttributes returns true if the path supports extended attributes.
|
||||||
|
func PathSupportsExtendedAttributes(path string) (supported bool, err error) {
|
||||||
|
var fileSystemFlags uint32
|
||||||
|
utf16Path, err := windows.UTF16PtrFromString(path)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
err = windows.GetVolumeInformation(utf16Path, nil, 0, nil, nil, &fileSystemFlags, nil, 0)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
supported = (fileSystemFlags & windows.FILE_SUPPORTS_EXTENDED_ATTRIBUTES) != 0
|
||||||
|
return supported, nil
|
||||||
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/restic/restic/internal/debug"
|
"github.com/restic/restic/internal/debug"
|
||||||
|
"github.com/restic/restic/internal/errors"
|
||||||
"golang.org/x/sys/windows"
|
"golang.org/x/sys/windows"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -60,6 +61,8 @@ func GetSecurityDescriptor(filePath string) (securityDescriptor *[]byte, err err
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("get low-level named security info failed with: %w", err)
|
return nil, fmt.Errorf("get low-level named security info failed with: %w", err)
|
||||||
}
|
}
|
||||||
|
} else if errors.Is(err, windows.ERROR_NOT_SUPPORTED) {
|
||||||
|
return nil, nil
|
||||||
} else {
|
} else {
|
||||||
return nil, fmt.Errorf("get named security info failed with: %w", err)
|
return nil, fmt.Errorf("get named security info failed with: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
|
@ -32,6 +33,15 @@ var (
|
||||||
modAdvapi32 = syscall.NewLazyDLL("advapi32.dll")
|
modAdvapi32 = syscall.NewLazyDLL("advapi32.dll")
|
||||||
procEncryptFile = modAdvapi32.NewProc("EncryptFileW")
|
procEncryptFile = modAdvapi32.NewProc("EncryptFileW")
|
||||||
procDecryptFile = modAdvapi32.NewProc("DecryptFileW")
|
procDecryptFile = modAdvapi32.NewProc("DecryptFileW")
|
||||||
|
|
||||||
|
// eaSupportedVolumesMap is a map of volumes to boolean values indicating if they support extended attributes.
|
||||||
|
eaSupportedVolumesMap = sync.Map{}
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
extendedPathPrefix = `\\?\`
|
||||||
|
uncPathPrefix = `\\?\UNC\`
|
||||||
|
globalRootPrefix = `\\?\GLOBALROOT\`
|
||||||
)
|
)
|
||||||
|
|
||||||
// mknod is not supported on Windows.
|
// mknod is not supported on Windows.
|
||||||
|
@ -351,32 +361,84 @@ func decryptFile(pathPointer *uint16) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// fillGenericAttributes fills in the generic attributes for windows like File Attributes,
|
// fillGenericAttributes fills in the generic attributes for windows like File Attributes,
|
||||||
// Created time etc.
|
// Created time and Security Descriptors.
|
||||||
|
// It also checks if the volume supports extended attributes and stores the result in a map
|
||||||
|
// so that it does not have to be checked again for subsequent calls for paths in the same volume.
|
||||||
func (node *Node) fillGenericAttributes(path string, fi os.FileInfo, stat *statT) (allowExtended bool, err error) {
|
func (node *Node) fillGenericAttributes(path string, fi os.FileInfo, stat *statT) (allowExtended bool, err error) {
|
||||||
if strings.Contains(filepath.Base(path), ":") {
|
if strings.Contains(filepath.Base(path), ":") {
|
||||||
//Do not process for Alternate Data Streams in Windows
|
// Do not process for Alternate Data Streams in Windows
|
||||||
// Also do not allow processing of extended attributes for ADS.
|
// Also do not allow processing of extended attributes for ADS.
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
if !strings.HasSuffix(filepath.Clean(path), `\`) {
|
|
||||||
// Do not process file attributes and created time for windows directories like
|
if strings.HasSuffix(filepath.Clean(path), `\`) {
|
||||||
// C:, D:
|
// filepath.Clean(path) ends with '\' for Windows root volume paths only
|
||||||
// Filepath.Clean(path) ends with '\' for Windows root drives only.
|
// Do not process file attributes, created time and sd for windows root volume paths
|
||||||
var sd *[]byte
|
// Security descriptors are not supported for root volume paths.
|
||||||
if node.Type == "file" || node.Type == "dir" {
|
// Though file attributes and created time are supported for root volume paths,
|
||||||
if sd, err = fs.GetSecurityDescriptor(path); err != nil {
|
// we ignore them and we do not want to replace them during every restore.
|
||||||
return true, err
|
allowExtended, err = checkAndStoreEASupport(path)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
}
|
}
|
||||||
|
return allowExtended, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var sd *[]byte
|
||||||
|
if node.Type == "file" || node.Type == "dir" {
|
||||||
|
// Check EA support and get security descriptor for file/dir only
|
||||||
|
allowExtended, err = checkAndStoreEASupport(path)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if sd, err = fs.GetSecurityDescriptor(path); err != nil {
|
||||||
|
return allowExtended, err
|
||||||
|
}
|
||||||
|
}
|
||||||
// Add Windows attributes
|
// Add Windows attributes
|
||||||
node.GenericAttributes, err = WindowsAttrsToGenericAttributes(WindowsAttributes{
|
node.GenericAttributes, err = WindowsAttrsToGenericAttributes(WindowsAttributes{
|
||||||
CreationTime: getCreationTime(fi, path),
|
CreationTime: getCreationTime(fi, path),
|
||||||
FileAttributes: &stat.FileAttributes,
|
FileAttributes: &stat.FileAttributes,
|
||||||
SecurityDescriptor: sd,
|
SecurityDescriptor: sd,
|
||||||
})
|
})
|
||||||
|
return allowExtended, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
func checkAndStoreEASupport(path string) (isEASupportedVolume bool, err error) {
|
||||||
|
// Check if it's an extended length path
|
||||||
|
if strings.HasPrefix(path, uncPathPrefix) {
|
||||||
|
// Convert \\?\UNC\ extended path to standard path to get the volume name correctly
|
||||||
|
path = `\\` + path[len(uncPathPrefix):]
|
||||||
|
} else if strings.HasPrefix(path, extendedPathPrefix) {
|
||||||
|
//Extended length path prefix needs to be trimmed to get the volume name correctly
|
||||||
|
path = path[len(extendedPathPrefix):]
|
||||||
|
} else if strings.HasPrefix(path, globalRootPrefix) {
|
||||||
|
// EAs are not supported for \\?\GLOBALROOT i.e. VSS snapshots
|
||||||
|
return false, nil
|
||||||
|
} else {
|
||||||
|
// Use the absolute path
|
||||||
|
path, err = filepath.Abs(path)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("failed to get absolute path: %w", err)
|
||||||
}
|
}
|
||||||
return true, err
|
}
|
||||||
|
volumeName := filepath.VolumeName(path)
|
||||||
|
if volumeName == "" {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
eaSupportedValue, exists := eaSupportedVolumesMap.Load(volumeName)
|
||||||
|
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