2024-02-23 00:55:50 +00:00
|
|
|
//go:build windows
|
|
|
|
// +build windows
|
|
|
|
|
2024-08-26 21:03:25 +00:00
|
|
|
package fs
|
2024-02-23 00:55:50 +00:00
|
|
|
|
|
|
|
import (
|
2024-02-24 20:27:01 +00:00
|
|
|
"encoding/base64"
|
2024-02-23 00:55:50 +00:00
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
2024-05-17 20:18:20 +00:00
|
|
|
"strings"
|
2024-02-23 00:55:50 +00:00
|
|
|
"syscall"
|
|
|
|
"testing"
|
2024-08-12 01:25:58 +00:00
|
|
|
"time"
|
2024-02-23 00:55:50 +00:00
|
|
|
|
|
|
|
"github.com/restic/restic/internal/errors"
|
2024-08-26 21:03:25 +00:00
|
|
|
"github.com/restic/restic/internal/restic"
|
2024-02-23 00:55:50 +00:00
|
|
|
"github.com/restic/restic/internal/test"
|
|
|
|
"golang.org/x/sys/windows"
|
|
|
|
)
|
|
|
|
|
2024-02-24 20:27:01 +00:00
|
|
|
func TestRestoreSecurityDescriptors(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
tempDir := t.TempDir()
|
2024-07-21 14:00:47 +00:00
|
|
|
for i, sd := range testFileSDs {
|
2024-07-09 17:51:44 +00:00
|
|
|
testRestoreSecurityDescriptor(t, sd, tempDir, restic.NodeTypeFile, fmt.Sprintf("testfile%d", i))
|
2024-02-24 20:27:01 +00:00
|
|
|
}
|
2024-07-21 14:00:47 +00:00
|
|
|
for i, sd := range testDirSDs {
|
2024-07-09 17:51:44 +00:00
|
|
|
testRestoreSecurityDescriptor(t, sd, tempDir, restic.NodeTypeDir, fmt.Sprintf("testdir%d", i))
|
2024-02-24 20:27:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-09 17:51:44 +00:00
|
|
|
func testRestoreSecurityDescriptor(t *testing.T, sd string, tempDir string, fileType restic.NodeType, fileName string) {
|
2024-02-24 20:27:01 +00:00
|
|
|
// Decode the encoded string SD to get the security descriptor input in bytes.
|
|
|
|
sdInputBytes, err := base64.StdEncoding.DecodeString(sd)
|
|
|
|
test.OK(t, errors.Wrapf(err, "Error decoding SD for: %s", fileName))
|
|
|
|
// Wrap the security descriptor bytes in windows attributes and convert to generic attributes.
|
|
|
|
genericAttributes, err := WindowsAttrsToGenericAttributes(WindowsAttributes{CreationTime: nil, FileAttributes: nil, SecurityDescriptor: &sdInputBytes})
|
|
|
|
test.OK(t, errors.Wrapf(err, "Error constructing windows attributes for: %s", fileName))
|
|
|
|
// Construct a Node with the generic attributes.
|
|
|
|
expectedNode := getNode(fileName, fileType, genericAttributes)
|
|
|
|
|
|
|
|
// Restore the file/dir and restore the meta data including the security descriptors.
|
2024-08-26 20:35:22 +00:00
|
|
|
testPath, node := restoreAndGetNode(t, tempDir, &expectedNode, false)
|
2024-02-24 20:27:01 +00:00
|
|
|
// Get the security descriptor from the node constructed from the file info of the restored path.
|
|
|
|
sdByteFromRestoredNode := getWindowsAttr(t, testPath, node).SecurityDescriptor
|
|
|
|
|
|
|
|
// Get the security descriptor for the test path after the restore.
|
2024-07-21 14:00:47 +00:00
|
|
|
sdBytesFromRestoredPath, err := getSecurityDescriptor(testPath)
|
2024-02-24 20:27:01 +00:00
|
|
|
test.OK(t, errors.Wrapf(err, "Error while getting the security descriptor for: %s", testPath))
|
|
|
|
|
|
|
|
// Compare the input SD and the SD got from the restored file.
|
2024-07-21 14:00:47 +00:00
|
|
|
compareSecurityDescriptors(t, testPath, sdInputBytes, *sdBytesFromRestoredPath)
|
2024-02-24 20:27:01 +00:00
|
|
|
// Compare the SD got from node constructed from the restored file info and the SD got directly from the restored file.
|
2024-07-21 14:00:47 +00:00
|
|
|
compareSecurityDescriptors(t, testPath, *sdByteFromRestoredNode, *sdBytesFromRestoredPath)
|
2024-02-24 20:27:01 +00:00
|
|
|
}
|
|
|
|
|
2024-07-09 17:51:44 +00:00
|
|
|
func getNode(name string, fileType restic.NodeType, genericAttributes map[restic.GenericAttributeType]json.RawMessage) restic.Node {
|
2024-08-26 21:03:25 +00:00
|
|
|
return restic.Node{
|
2024-02-24 20:27:01 +00:00
|
|
|
Name: name,
|
|
|
|
Type: fileType,
|
|
|
|
Mode: 0644,
|
|
|
|
ModTime: parseTime("2024-02-21 6:30:01.111"),
|
|
|
|
AccessTime: parseTime("2024-02-22 7:31:02.222"),
|
|
|
|
ChangeTime: parseTime("2024-02-23 8:32:03.333"),
|
|
|
|
GenericAttributes: genericAttributes,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-08-26 21:03:25 +00:00
|
|
|
func getWindowsAttr(t *testing.T, testPath string, node *restic.Node) WindowsAttributes {
|
2024-02-24 20:27:01 +00:00
|
|
|
windowsAttributes, unknownAttribs, err := genericAttributesToWindowsAttrs(node.GenericAttributes)
|
|
|
|
test.OK(t, errors.Wrapf(err, "Error getting windows attr from generic attr: %s", testPath))
|
2024-07-01 22:45:59 +00:00
|
|
|
test.Assert(t, len(unknownAttribs) == 0, "Unknown attribs found: %s for: %s", unknownAttribs, testPath)
|
2024-02-24 20:27:01 +00:00
|
|
|
return windowsAttributes
|
|
|
|
}
|
|
|
|
|
2024-02-23 00:55:50 +00:00
|
|
|
func TestRestoreCreationTime(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
path := t.TempDir()
|
|
|
|
fi, err := os.Lstat(path)
|
|
|
|
test.OK(t, errors.Wrapf(err, "Could not Lstat for path: %s", path))
|
|
|
|
creationTimeAttribute := getCreationTime(fi, path)
|
|
|
|
test.OK(t, errors.Wrapf(err, "Could not get creation time for path: %s", path))
|
|
|
|
//Using the temp dir creation time as the test creation time for the test file and folder
|
2024-08-26 21:03:25 +00:00
|
|
|
runGenericAttributesTest(t, path, restic.TypeCreationTime, WindowsAttributes{CreationTime: creationTimeAttribute}, false)
|
2024-02-23 00:55:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestRestoreFileAttributes(t *testing.T) {
|
|
|
|
t.Parallel()
|
2024-08-26 21:03:25 +00:00
|
|
|
genericAttributeName := restic.TypeFileAttributes
|
2024-02-23 00:55:50 +00:00
|
|
|
tempDir := t.TempDir()
|
|
|
|
normal := uint32(syscall.FILE_ATTRIBUTE_NORMAL)
|
|
|
|
hidden := uint32(syscall.FILE_ATTRIBUTE_HIDDEN)
|
|
|
|
system := uint32(syscall.FILE_ATTRIBUTE_SYSTEM)
|
|
|
|
archive := uint32(syscall.FILE_ATTRIBUTE_ARCHIVE)
|
|
|
|
encrypted := uint32(windows.FILE_ATTRIBUTE_ENCRYPTED)
|
|
|
|
fileAttributes := []WindowsAttributes{
|
|
|
|
//normal
|
|
|
|
{FileAttributes: &normal},
|
|
|
|
//hidden
|
|
|
|
{FileAttributes: &hidden},
|
|
|
|
//system
|
|
|
|
{FileAttributes: &system},
|
|
|
|
//archive
|
|
|
|
{FileAttributes: &archive},
|
|
|
|
//encrypted
|
|
|
|
{FileAttributes: &encrypted},
|
|
|
|
}
|
|
|
|
for i, fileAttr := range fileAttributes {
|
|
|
|
genericAttrs, err := WindowsAttrsToGenericAttributes(fileAttr)
|
|
|
|
test.OK(t, err)
|
2024-08-26 21:03:25 +00:00
|
|
|
expectedNodes := []restic.Node{
|
2024-02-23 00:55:50 +00:00
|
|
|
{
|
|
|
|
Name: fmt.Sprintf("testfile%d", i),
|
2024-07-09 17:51:44 +00:00
|
|
|
Type: restic.NodeTypeFile,
|
2024-02-23 00:55:50 +00:00
|
|
|
Mode: 0655,
|
|
|
|
ModTime: parseTime("2005-05-14 21:07:03.111"),
|
|
|
|
AccessTime: parseTime("2005-05-14 21:07:04.222"),
|
|
|
|
ChangeTime: parseTime("2005-05-14 21:07:05.333"),
|
|
|
|
GenericAttributes: genericAttrs,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
runGenericAttributesTestForNodes(t, expectedNodes, tempDir, genericAttributeName, fileAttr, false)
|
|
|
|
}
|
|
|
|
normal = uint32(syscall.FILE_ATTRIBUTE_DIRECTORY)
|
|
|
|
hidden = uint32(syscall.FILE_ATTRIBUTE_DIRECTORY | syscall.FILE_ATTRIBUTE_HIDDEN)
|
|
|
|
system = uint32(syscall.FILE_ATTRIBUTE_DIRECTORY | windows.FILE_ATTRIBUTE_SYSTEM)
|
|
|
|
archive = uint32(syscall.FILE_ATTRIBUTE_DIRECTORY | windows.FILE_ATTRIBUTE_ARCHIVE)
|
|
|
|
encrypted = uint32(syscall.FILE_ATTRIBUTE_DIRECTORY | windows.FILE_ATTRIBUTE_ENCRYPTED)
|
|
|
|
folderAttributes := []WindowsAttributes{
|
|
|
|
//normal
|
|
|
|
{FileAttributes: &normal},
|
|
|
|
//hidden
|
|
|
|
{FileAttributes: &hidden},
|
|
|
|
//system
|
|
|
|
{FileAttributes: &system},
|
|
|
|
//archive
|
|
|
|
{FileAttributes: &archive},
|
|
|
|
//encrypted
|
|
|
|
{FileAttributes: &encrypted},
|
|
|
|
}
|
|
|
|
for i, folderAttr := range folderAttributes {
|
|
|
|
genericAttrs, err := WindowsAttrsToGenericAttributes(folderAttr)
|
|
|
|
test.OK(t, err)
|
2024-08-26 21:03:25 +00:00
|
|
|
expectedNodes := []restic.Node{
|
2024-02-23 00:55:50 +00:00
|
|
|
{
|
|
|
|
Name: fmt.Sprintf("testdirectory%d", i),
|
2024-07-09 17:51:44 +00:00
|
|
|
Type: restic.NodeTypeDir,
|
2024-02-23 00:55:50 +00:00
|
|
|
Mode: 0755,
|
|
|
|
ModTime: parseTime("2005-05-14 21:07:03.111"),
|
|
|
|
AccessTime: parseTime("2005-05-14 21:07:04.222"),
|
|
|
|
ChangeTime: parseTime("2005-05-14 21:07:05.333"),
|
|
|
|
GenericAttributes: genericAttrs,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
runGenericAttributesTestForNodes(t, expectedNodes, tempDir, genericAttributeName, folderAttr, false)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-08-26 21:03:25 +00:00
|
|
|
func runGenericAttributesTest(t *testing.T, tempDir string, genericAttributeName restic.GenericAttributeType, genericAttributeExpected WindowsAttributes, warningExpected bool) {
|
2024-02-23 00:55:50 +00:00
|
|
|
genericAttributes, err := WindowsAttrsToGenericAttributes(genericAttributeExpected)
|
|
|
|
test.OK(t, err)
|
2024-08-26 21:03:25 +00:00
|
|
|
expectedNodes := []restic.Node{
|
2024-02-23 00:55:50 +00:00
|
|
|
{
|
|
|
|
Name: "testfile",
|
2024-07-09 17:51:44 +00:00
|
|
|
Type: restic.NodeTypeFile,
|
2024-02-23 00:55:50 +00:00
|
|
|
Mode: 0644,
|
|
|
|
ModTime: parseTime("2005-05-14 21:07:03.111"),
|
|
|
|
AccessTime: parseTime("2005-05-14 21:07:04.222"),
|
|
|
|
ChangeTime: parseTime("2005-05-14 21:07:05.333"),
|
|
|
|
GenericAttributes: genericAttributes,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "testdirectory",
|
2024-07-09 17:51:44 +00:00
|
|
|
Type: restic.NodeTypeDir,
|
2024-02-23 00:55:50 +00:00
|
|
|
Mode: 0755,
|
|
|
|
ModTime: parseTime("2005-05-14 21:07:03.111"),
|
|
|
|
AccessTime: parseTime("2005-05-14 21:07:04.222"),
|
|
|
|
ChangeTime: parseTime("2005-05-14 21:07:05.333"),
|
|
|
|
GenericAttributes: genericAttributes,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
runGenericAttributesTestForNodes(t, expectedNodes, tempDir, genericAttributeName, genericAttributeExpected, warningExpected)
|
|
|
|
}
|
2024-08-26 21:03:25 +00:00
|
|
|
func runGenericAttributesTestForNodes(t *testing.T, expectedNodes []restic.Node, tempDir string, genericAttr restic.GenericAttributeType, genericAttributeExpected WindowsAttributes, warningExpected bool) {
|
2024-02-23 00:55:50 +00:00
|
|
|
|
|
|
|
for _, testNode := range expectedNodes {
|
2024-08-26 20:35:22 +00:00
|
|
|
testPath, node := restoreAndGetNode(t, tempDir, &testNode, warningExpected)
|
2024-02-23 00:55:50 +00:00
|
|
|
rawMessage := node.GenericAttributes[genericAttr]
|
|
|
|
genericAttrsExpected, err := WindowsAttrsToGenericAttributes(genericAttributeExpected)
|
|
|
|
test.OK(t, err)
|
|
|
|
rawMessageExpected := genericAttrsExpected[genericAttr]
|
|
|
|
test.Equals(t, rawMessageExpected, rawMessage, "Generic attribute: %s got from NodeFromFileInfo not equal for path: %s", string(genericAttr), testPath)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-08-26 21:03:25 +00:00
|
|
|
func restoreAndGetNode(t *testing.T, tempDir string, testNode *restic.Node, warningExpected bool) (string, *restic.Node) {
|
2024-02-23 00:55:50 +00:00
|
|
|
testPath := filepath.Join(tempDir, "001", testNode.Name)
|
|
|
|
err := os.MkdirAll(filepath.Dir(testPath), testNode.Mode)
|
|
|
|
test.OK(t, errors.Wrapf(err, "Failed to create parent directories for: %s", testPath))
|
|
|
|
|
2024-07-09 17:51:44 +00:00
|
|
|
if testNode.Type == restic.NodeTypeFile {
|
2024-02-23 00:55:50 +00:00
|
|
|
|
|
|
|
testFile, err := os.Create(testPath)
|
|
|
|
test.OK(t, errors.Wrapf(err, "Failed to create test file: %s", testPath))
|
|
|
|
testFile.Close()
|
2024-07-09 17:51:44 +00:00
|
|
|
} else if testNode.Type == restic.NodeTypeDir {
|
2024-02-23 00:55:50 +00:00
|
|
|
|
|
|
|
err := os.Mkdir(testPath, testNode.Mode)
|
|
|
|
test.OK(t, errors.Wrapf(err, "Failed to create test directory: %s", testPath))
|
|
|
|
}
|
|
|
|
|
2024-08-26 20:35:22 +00:00
|
|
|
err = NodeRestoreMetadata(testNode, testPath, func(msg string) {
|
2024-02-23 00:55:50 +00:00
|
|
|
if warningExpected {
|
|
|
|
test.Assert(t, warningExpected, "Warning triggered as expected: %s", msg)
|
|
|
|
} else {
|
|
|
|
// If warning is not expected, this code should not get triggered.
|
|
|
|
test.OK(t, fmt.Errorf("Warning triggered for path: %s: %s", testPath, msg))
|
|
|
|
}
|
|
|
|
})
|
|
|
|
test.OK(t, errors.Wrapf(err, "Failed to restore metadata for: %s", testPath))
|
|
|
|
|
|
|
|
fi, err := os.Lstat(testPath)
|
|
|
|
test.OK(t, errors.Wrapf(err, "Could not Lstat for path: %s", testPath))
|
|
|
|
|
2024-01-31 19:48:03 +00:00
|
|
|
nodeFromFileInfo, err := NodeFromFileInfo(testPath, fi, false)
|
2024-02-23 00:55:50 +00:00
|
|
|
test.OK(t, errors.Wrapf(err, "Could not get NodeFromFileInfo for path: %s", testPath))
|
|
|
|
|
|
|
|
return testPath, nodeFromFileInfo
|
|
|
|
}
|
|
|
|
|
2024-08-26 21:03:25 +00:00
|
|
|
const TypeSomeNewAttribute restic.GenericAttributeType = "MockAttributes.SomeNewAttribute"
|
2024-02-23 00:55:50 +00:00
|
|
|
|
|
|
|
func TestNewGenericAttributeType(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
2024-08-26 21:03:25 +00:00
|
|
|
newGenericAttribute := map[restic.GenericAttributeType]json.RawMessage{}
|
2024-02-23 00:55:50 +00:00
|
|
|
newGenericAttribute[TypeSomeNewAttribute] = []byte("any value")
|
|
|
|
|
|
|
|
tempDir := t.TempDir()
|
2024-08-26 21:03:25 +00:00
|
|
|
expectedNodes := []restic.Node{
|
2024-02-23 00:55:50 +00:00
|
|
|
{
|
|
|
|
Name: "testfile",
|
2024-07-09 17:51:44 +00:00
|
|
|
Type: restic.NodeTypeFile,
|
2024-02-23 00:55:50 +00:00
|
|
|
Mode: 0644,
|
|
|
|
ModTime: parseTime("2005-05-14 21:07:03.111"),
|
|
|
|
AccessTime: parseTime("2005-05-14 21:07:04.222"),
|
|
|
|
ChangeTime: parseTime("2005-05-14 21:07:05.333"),
|
|
|
|
GenericAttributes: newGenericAttribute,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "testdirectory",
|
2024-07-09 17:51:44 +00:00
|
|
|
Type: restic.NodeTypeDir,
|
2024-02-23 00:55:50 +00:00
|
|
|
Mode: 0755,
|
|
|
|
ModTime: parseTime("2005-05-14 21:07:03.111"),
|
|
|
|
AccessTime: parseTime("2005-05-14 21:07:04.222"),
|
|
|
|
ChangeTime: parseTime("2005-05-14 21:07:05.333"),
|
|
|
|
GenericAttributes: newGenericAttribute,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
for _, testNode := range expectedNodes {
|
2024-08-26 20:35:22 +00:00
|
|
|
testPath, node := restoreAndGetNode(t, tempDir, &testNode, true)
|
2024-02-23 00:55:50 +00:00
|
|
|
_, ua, err := genericAttributesToWindowsAttrs(node.GenericAttributes)
|
|
|
|
test.OK(t, err)
|
|
|
|
// Since this GenericAttribute is unknown to this version of the software, it will not get set on the file.
|
2024-07-01 22:45:59 +00:00
|
|
|
test.Assert(t, len(ua) == 0, "Unknown attributes: %s found for path: %s", ua, testPath)
|
2024-02-23 00:55:50 +00:00
|
|
|
}
|
|
|
|
}
|
2024-05-17 20:18:20 +00:00
|
|
|
|
|
|
|
func TestRestoreExtendedAttributes(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
tempDir := t.TempDir()
|
2024-08-26 21:03:25 +00:00
|
|
|
expectedNodes := []restic.Node{
|
2024-05-17 20:18:20 +00:00
|
|
|
{
|
|
|
|
Name: "testfile",
|
2024-07-09 17:51:44 +00:00
|
|
|
Type: restic.NodeTypeFile,
|
2024-05-17 20:18:20 +00:00
|
|
|
Mode: 0644,
|
|
|
|
ModTime: parseTime("2005-05-14 21:07:03.111"),
|
|
|
|
AccessTime: parseTime("2005-05-14 21:07:04.222"),
|
|
|
|
ChangeTime: parseTime("2005-05-14 21:07:05.333"),
|
2024-08-26 21:03:25 +00:00
|
|
|
ExtendedAttributes: []restic.ExtendedAttribute{
|
2024-05-17 20:18:20 +00:00
|
|
|
{"user.foo", []byte("bar")},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "testdirectory",
|
2024-07-09 17:51:44 +00:00
|
|
|
Type: restic.NodeTypeDir,
|
2024-05-17 20:18:20 +00:00
|
|
|
Mode: 0755,
|
|
|
|
ModTime: parseTime("2005-05-14 21:07:03.111"),
|
|
|
|
AccessTime: parseTime("2005-05-14 21:07:04.222"),
|
|
|
|
ChangeTime: parseTime("2005-05-14 21:07:05.333"),
|
2024-08-26 21:03:25 +00:00
|
|
|
ExtendedAttributes: []restic.ExtendedAttribute{
|
2024-05-17 20:18:20 +00:00
|
|
|
{"user.foo", []byte("bar")},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
for _, testNode := range expectedNodes {
|
2024-08-26 20:35:22 +00:00
|
|
|
testPath, node := restoreAndGetNode(t, tempDir, &testNode, false)
|
2024-05-17 20:18:20 +00:00
|
|
|
|
|
|
|
var handle windows.Handle
|
|
|
|
var err error
|
|
|
|
utf16Path := windows.StringToUTF16Ptr(testPath)
|
2024-07-09 17:51:44 +00:00
|
|
|
if node.Type == restic.NodeTypeFile {
|
2024-05-17 20:18:20 +00:00
|
|
|
handle, err = windows.CreateFile(utf16Path, windows.FILE_READ_EA, 0, nil, windows.OPEN_EXISTING, windows.FILE_ATTRIBUTE_NORMAL, 0)
|
2024-07-09 17:51:44 +00:00
|
|
|
} else if node.Type == restic.NodeTypeDir {
|
2024-05-17 20:18:20 +00:00
|
|
|
handle, err = windows.CreateFile(utf16Path, windows.FILE_READ_EA, 0, nil, windows.OPEN_EXISTING, windows.FILE_ATTRIBUTE_NORMAL|windows.FILE_FLAG_BACKUP_SEMANTICS, 0)
|
|
|
|
}
|
|
|
|
test.OK(t, errors.Wrapf(err, "Error opening file/directory for: %s", testPath))
|
|
|
|
defer func() {
|
|
|
|
err := windows.Close(handle)
|
|
|
|
test.OK(t, errors.Wrapf(err, "Error closing file for: %s", testPath))
|
|
|
|
}()
|
|
|
|
|
2024-07-21 14:00:47 +00:00
|
|
|
extAttr, err := fgetEA(handle)
|
2024-06-05 22:06:57 +00:00
|
|
|
test.OK(t, errors.Wrapf(err, "Error getting extended attributes for: %s", testPath))
|
|
|
|
test.Equals(t, len(node.ExtendedAttributes), len(extAttr))
|
2024-05-17 20:18:20 +00:00
|
|
|
|
2024-06-05 22:06:57 +00:00
|
|
|
for _, expectedExtAttr := range node.ExtendedAttributes {
|
2024-07-21 14:00:47 +00:00
|
|
|
var foundExtAttr *extendedAttribute
|
2024-06-05 22:06:57 +00:00
|
|
|
for _, ea := range extAttr {
|
|
|
|
if strings.EqualFold(ea.Name, expectedExtAttr.Name) {
|
|
|
|
foundExtAttr = &ea
|
|
|
|
break
|
2024-05-17 20:18:20 +00:00
|
|
|
|
|
|
|
}
|
|
|
|
}
|
2024-06-05 22:06:57 +00:00
|
|
|
test.Assert(t, foundExtAttr != nil, "Expected extended attribute not found")
|
|
|
|
test.Equals(t, expectedExtAttr.Value, foundExtAttr.Value)
|
2024-05-17 20:18:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-08-12 01:25:58 +00:00
|
|
|
|
|
|
|
func TestPrepareVolumeName(t *testing.T) {
|
|
|
|
currentVolume := filepath.VolumeName(func() string {
|
|
|
|
// Get the current working directory
|
|
|
|
pwd, err := os.Getwd()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Failed to get current working directory: %v", err)
|
|
|
|
}
|
|
|
|
return pwd
|
|
|
|
}())
|
|
|
|
// Create a temporary directory for the test
|
|
|
|
tempDir, err := os.MkdirTemp("", "restic_test_"+time.Now().Format("20060102150405"))
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Failed to create temp directory: %v", err)
|
|
|
|
}
|
|
|
|
defer os.RemoveAll(tempDir)
|
|
|
|
|
|
|
|
// Create a long file name
|
|
|
|
longFileName := `\Very\Long\Path\That\Exceeds\260\Characters\` + strings.Repeat(`\VeryLongFolderName`, 20) + `\\LongFile.txt`
|
|
|
|
longFilePath := filepath.Join(tempDir, longFileName)
|
|
|
|
|
|
|
|
tempDirVolume := filepath.VolumeName(tempDir)
|
|
|
|
// Create the file
|
|
|
|
content := []byte("This is a test file with a very long name.")
|
|
|
|
err = os.MkdirAll(filepath.Dir(longFilePath), 0755)
|
|
|
|
test.OK(t, err)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Failed to create long folder: %v", err)
|
|
|
|
}
|
|
|
|
err = os.WriteFile(longFilePath, content, 0644)
|
|
|
|
test.OK(t, err)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Failed to create long file: %v", err)
|
|
|
|
}
|
|
|
|
osVolumeGUIDPath := getOSVolumeGUIDPath(t)
|
|
|
|
osVolumeGUIDVolume := filepath.VolumeName(osVolumeGUIDPath)
|
|
|
|
|
|
|
|
testCases := []struct {
|
|
|
|
name string
|
|
|
|
path string
|
|
|
|
expectedVolume string
|
|
|
|
expectError bool
|
|
|
|
expectedEASupported bool
|
|
|
|
isRealPath bool
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "Network drive path",
|
|
|
|
path: `Z:\Shared\Documents`,
|
|
|
|
expectedVolume: `Z:`,
|
|
|
|
expectError: false,
|
|
|
|
expectedEASupported: false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Subst drive path",
|
|
|
|
path: `X:\Virtual\Folder`,
|
|
|
|
expectedVolume: `X:`,
|
|
|
|
expectError: false,
|
|
|
|
expectedEASupported: false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Windows reserved path",
|
|
|
|
path: `\\.\` + os.Getenv("SystemDrive") + `\System32\drivers\etc\hosts`,
|
|
|
|
expectedVolume: `\\.\` + os.Getenv("SystemDrive"),
|
|
|
|
expectError: false,
|
|
|
|
expectedEASupported: true,
|
|
|
|
isRealPath: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Long UNC path",
|
|
|
|
path: `\\?\UNC\LongServerName\VeryLongShareName\DeepPath\File.txt`,
|
|
|
|
expectedVolume: `\\LongServerName\VeryLongShareName`,
|
|
|
|
expectError: false,
|
|
|
|
expectedEASupported: false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Volume GUID path",
|
|
|
|
path: osVolumeGUIDPath,
|
|
|
|
expectedVolume: osVolumeGUIDVolume,
|
|
|
|
expectError: false,
|
|
|
|
expectedEASupported: true,
|
|
|
|
isRealPath: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Volume GUID path with subfolder",
|
|
|
|
path: osVolumeGUIDPath + `\Windows`,
|
|
|
|
expectedVolume: osVolumeGUIDVolume,
|
|
|
|
expectError: false,
|
|
|
|
expectedEASupported: true,
|
|
|
|
isRealPath: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Standard path",
|
|
|
|
path: os.Getenv("SystemDrive") + `\Users\`,
|
|
|
|
expectedVolume: os.Getenv("SystemDrive"),
|
|
|
|
expectError: false,
|
|
|
|
expectedEASupported: true,
|
|
|
|
isRealPath: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Extended length path",
|
|
|
|
path: longFilePath,
|
|
|
|
expectedVolume: tempDirVolume,
|
|
|
|
expectError: false,
|
|
|
|
expectedEASupported: true,
|
|
|
|
isRealPath: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "UNC path",
|
|
|
|
path: `\\server\share\folder`,
|
|
|
|
expectedVolume: `\\server\share`,
|
|
|
|
expectError: false,
|
|
|
|
expectedEASupported: false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Extended UNC path",
|
|
|
|
path: `\\?\UNC\server\share\folder`,
|
|
|
|
expectedVolume: `\\server\share`,
|
|
|
|
expectError: false,
|
|
|
|
expectedEASupported: false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Volume Shadow Copy path",
|
|
|
|
path: `\\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy1\Users\test`,
|
|
|
|
expectedVolume: `\\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy1`,
|
|
|
|
expectError: false,
|
|
|
|
expectedEASupported: false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Relative path",
|
|
|
|
path: `folder\subfolder`,
|
|
|
|
|
|
|
|
expectedVolume: currentVolume, // Get current volume
|
|
|
|
expectError: false,
|
|
|
|
expectedEASupported: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Empty path",
|
|
|
|
path: ``,
|
|
|
|
expectedVolume: currentVolume,
|
|
|
|
expectError: false,
|
|
|
|
expectedEASupported: true,
|
|
|
|
isRealPath: false,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tc := range testCases {
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
|
|
isEASupported, err := checkAndStoreEASupport(tc.path)
|
|
|
|
test.OK(t, err)
|
|
|
|
test.Equals(t, tc.expectedEASupported, isEASupported)
|
|
|
|
|
|
|
|
volume, err := prepareVolumeName(tc.path)
|
|
|
|
|
|
|
|
if tc.expectError {
|
|
|
|
test.Assert(t, err != nil, "Expected an error, but got none")
|
|
|
|
} else {
|
|
|
|
test.OK(t, err)
|
|
|
|
}
|
|
|
|
test.Equals(t, tc.expectedVolume, volume)
|
|
|
|
|
|
|
|
if tc.isRealPath {
|
2024-07-21 14:00:47 +00:00
|
|
|
isEASupportedVolume, err := pathSupportsExtendedAttributes(volume + `\`)
|
2024-08-12 01:25:58 +00:00
|
|
|
// If the prepared volume name is not valid, we will next fetch the actual volume name.
|
|
|
|
test.OK(t, err)
|
|
|
|
|
|
|
|
test.Equals(t, tc.expectedEASupported, isEASupportedVolume)
|
|
|
|
|
2024-07-21 14:00:47 +00:00
|
|
|
actualVolume, err := getVolumePathName(tc.path)
|
2024-08-12 01:25:58 +00:00
|
|
|
test.OK(t, err)
|
|
|
|
test.Equals(t, tc.expectedVolume, actualVolume)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func getOSVolumeGUIDPath(t *testing.T) string {
|
|
|
|
// Get the path of the OS drive (usually C:\)
|
|
|
|
osDrive := os.Getenv("SystemDrive") + "\\"
|
|
|
|
|
|
|
|
// Convert to a volume GUID path
|
|
|
|
volumeName, err := windows.UTF16PtrFromString(osDrive)
|
|
|
|
test.OK(t, err)
|
|
|
|
if err != nil {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
var volumeGUID [windows.MAX_PATH]uint16
|
|
|
|
err = windows.GetVolumeNameForVolumeMountPoint(volumeName, &volumeGUID[0], windows.MAX_PATH)
|
|
|
|
test.OK(t, err)
|
|
|
|
if err != nil {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
return windows.UTF16ToString(volumeGUID[:])
|
|
|
|
}
|