diff --git a/internal/fs/ea_windows.go b/internal/fs/ea_windows.go index 08466c33f..e69f595a8 100644 --- a/internal/fs/ea_windows.go +++ b/internal/fs/ea_windows.go @@ -53,6 +53,15 @@ var ( errInvalidEaBuffer = errors.New("invalid extended attribute buffer") errEaNameTooLarge = errors.New("extended attribute name too large") errEaValueTooLarge = errors.New("extended attribute value too large") + + kernel32dll = syscall.NewLazyDLL("kernel32.dll") + + procGetVolumeInformationW = kernel32dll.NewProc("GetVolumeInformationW") +) + +const ( + // fileSupportsExtendedAttributes is a bitmask that indicates whether the file system supports extended attributes. + fileSupportsExtendedAttributes = 0x00000004 ) // ExtendedAttribute represents a single Windows EA. @@ -283,3 +292,34 @@ func setFileEA(handle windows.Handle, iosb *ioStatusBlock, buf *uint8, bufLen ui status = ntStatus(r0) return } + +// PathSupportsExtendedAttributes returns true if the path supports extended attributes. +func PathSupportsExtendedAttributes(path string) (bool, error) { + var ( + volumeName [syscall.MAX_PATH + 1]uint16 + fsName [syscall.MAX_PATH + 1]uint16 + volumeSerial uint32 + maxComponentLen uint32 + fileSystemFlags uint32 + ) + utf16Path, err := windows.UTF16PtrFromString(path) + if err != nil { + return false, err + } + ret, _, err := procGetVolumeInformationW.Call( + uintptr(unsafe.Pointer(utf16Path)), + uintptr(unsafe.Pointer(&volumeName[0])), + uintptr(len(volumeName)), + uintptr(unsafe.Pointer(&volumeSerial)), + uintptr(unsafe.Pointer(&maxComponentLen)), + uintptr(unsafe.Pointer(&fileSystemFlags)), + uintptr(unsafe.Pointer(&fsName[0])), + uintptr(len(fsName)), + ) + if ret == 0 { + return false, err + } + + supportsEAs := (fileSystemFlags & fileSupportsExtendedAttributes) != 0 + return supportsEAs, nil +} diff --git a/internal/restic/node_windows.go b/internal/restic/node_windows.go index 2785e0412..ea167b96d 100644 --- a/internal/restic/node_windows.go +++ b/internal/restic/node_windows.go @@ -32,6 +32,9 @@ var ( modAdvapi32 = syscall.NewLazyDLL("advapi32.dll") procEncryptFile = modAdvapi32.NewProc("EncryptFileW") procDecryptFile = modAdvapi32.NewProc("DecryptFileW") + + // eaUnsupportedVolumesMap is a map of volumes that do not support extended attributes. + eaUnsupportedVolumesMap = map[string]bool{} ) // mknod is not supported on Windows. @@ -358,7 +361,20 @@ func (node *Node) fillGenericAttributes(path string, fi os.FileInfo, stat *statT // Also do not allow processing of extended attributes for ADS. return false, nil } - if !strings.HasSuffix(filepath.Clean(path), `\`) { + if strings.HasSuffix(filepath.Clean(path), `\`) { + // This path is a volume + supportsEAs, err := fs.PathSupportsExtendedAttributes(path) + if err != nil { + return false, err + } + if supportsEAs { + delete(eaUnsupportedVolumesMap, filepath.VolumeName(path)) + } else { + // Add the volume to the map of volumes that do not support extended attributes. + eaUnsupportedVolumesMap[filepath.VolumeName(path)] = true + } + return supportsEAs, nil + } else { // Do not process file attributes and created time for windows directories like // C:, D: // Filepath.Clean(path) ends with '\' for Windows root drives only. @@ -375,8 +391,8 @@ func (node *Node) fillGenericAttributes(path string, fi os.FileInfo, stat *statT FileAttributes: &stat.FileAttributes, SecurityDescriptor: sd, }) + return !eaUnsupportedVolumesMap[filepath.VolumeName(path)], err } - return true, err } // windowsAttrsToGenericAttributes converts the WindowsAttributes to a generic attributes map using reflection