forked from TrueCloudLab/restic
Add tests for generic attribute changes
This commit is contained in:
parent
d4be734c73
commit
4bbd25a37f
4 changed files with 770 additions and 36 deletions
210
internal/restic/node_windows_test.go
Normal file
210
internal/restic/node_windows_test.go
Normal file
|
@ -0,0 +1,210 @@
|
|||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package restic
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
"github.com/restic/restic/internal/errors"
|
||||
"github.com/restic/restic/internal/test"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
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
|
||||
runGenericAttributesTest(t, path, TypeCreationTime, WindowsAttributes{CreationTime: creationTimeAttribute}, false)
|
||||
}
|
||||
|
||||
func TestRestoreFileAttributes(t *testing.T) {
|
||||
t.Parallel()
|
||||
genericAttributeName := TypeFileAttributes
|
||||
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)
|
||||
expectedNodes := []Node{
|
||||
{
|
||||
Name: fmt.Sprintf("testfile%d", i),
|
||||
Type: "file",
|
||||
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)
|
||||
expectedNodes := []Node{
|
||||
{
|
||||
Name: fmt.Sprintf("testdirectory%d", i),
|
||||
Type: "dir",
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
func runGenericAttributesTest(t *testing.T, tempDir string, genericAttributeName GenericAttributeType, genericAttributeExpected WindowsAttributes, warningExpected bool) {
|
||||
genericAttributes, err := WindowsAttrsToGenericAttributes(genericAttributeExpected)
|
||||
test.OK(t, err)
|
||||
expectedNodes := []Node{
|
||||
{
|
||||
Name: "testfile",
|
||||
Type: "file",
|
||||
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",
|
||||
Type: "dir",
|
||||
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)
|
||||
}
|
||||
func runGenericAttributesTestForNodes(t *testing.T, expectedNodes []Node, tempDir string, genericAttr GenericAttributeType, genericAttributeExpected WindowsAttributes, warningExpected bool) {
|
||||
|
||||
for _, testNode := range expectedNodes {
|
||||
testPath, node := restoreAndGetNode(t, tempDir, testNode, warningExpected)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
func restoreAndGetNode(t *testing.T, tempDir string, testNode Node, warningExpected bool) (string, *Node) {
|
||||
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))
|
||||
|
||||
if testNode.Type == "file" {
|
||||
|
||||
testFile, err := os.Create(testPath)
|
||||
test.OK(t, errors.Wrapf(err, "Failed to create test file: %s", testPath))
|
||||
testFile.Close()
|
||||
} else if testNode.Type == "dir" {
|
||||
|
||||
err := os.Mkdir(testPath, testNode.Mode)
|
||||
test.OK(t, errors.Wrapf(err, "Failed to create test directory: %s", testPath))
|
||||
}
|
||||
|
||||
err = testNode.RestoreMetadata(testPath, func(msg string) {
|
||||
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))
|
||||
|
||||
nodeFromFileInfo, err := NodeFromFileInfo(testPath, fi)
|
||||
test.OK(t, errors.Wrapf(err, "Could not get NodeFromFileInfo for path: %s", testPath))
|
||||
|
||||
return testPath, nodeFromFileInfo
|
||||
}
|
||||
|
||||
const TypeSomeNewAttribute GenericAttributeType = "MockAttributes.SomeNewAttribute"
|
||||
|
||||
func TestNewGenericAttributeType(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
newGenericAttribute := map[GenericAttributeType]json.RawMessage{}
|
||||
newGenericAttribute[TypeSomeNewAttribute] = []byte("any value")
|
||||
|
||||
tempDir := t.TempDir()
|
||||
expectedNodes := []Node{
|
||||
{
|
||||
Name: "testfile",
|
||||
Type: "file",
|
||||
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",
|
||||
Type: "dir",
|
||||
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 {
|
||||
testPath, node := restoreAndGetNode(t, tempDir, testNode, true)
|
||||
_, 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.
|
||||
test.Assert(t, len(ua) == 0, "Unkown attributes: %s found for path: %s", ua, testPath)
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@ package restorer
|
|||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"math"
|
||||
"os"
|
||||
|
@ -27,17 +28,27 @@ type Snapshot struct {
|
|||
}
|
||||
|
||||
type File struct {
|
||||
Data string
|
||||
Links uint64
|
||||
Inode uint64
|
||||
Mode os.FileMode
|
||||
ModTime time.Time
|
||||
Data string
|
||||
Links uint64
|
||||
Inode uint64
|
||||
Mode os.FileMode
|
||||
ModTime time.Time
|
||||
attributes *FileAttributes
|
||||
}
|
||||
|
||||
type Dir struct {
|
||||
Nodes map[string]Node
|
||||
Mode os.FileMode
|
||||
ModTime time.Time
|
||||
Nodes map[string]Node
|
||||
Mode os.FileMode
|
||||
ModTime time.Time
|
||||
attributes *FileAttributes
|
||||
}
|
||||
|
||||
type FileAttributes struct {
|
||||
ReadOnly bool
|
||||
Hidden bool
|
||||
System bool
|
||||
Archive bool
|
||||
Encrypted bool
|
||||
}
|
||||
|
||||
func saveFile(t testing.TB, repo restic.BlobSaver, node File) restic.ID {
|
||||
|
@ -52,7 +63,7 @@ func saveFile(t testing.TB, repo restic.BlobSaver, node File) restic.ID {
|
|||
return id
|
||||
}
|
||||
|
||||
func saveDir(t testing.TB, repo restic.BlobSaver, nodes map[string]Node, inode uint64) restic.ID {
|
||||
func saveDir(t testing.TB, repo restic.BlobSaver, nodes map[string]Node, inode uint64, getGenericAttributes func(attr *FileAttributes, isDir bool) (genericAttributes map[restic.GenericAttributeType]json.RawMessage)) restic.ID {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
|
@ -78,20 +89,21 @@ func saveDir(t testing.TB, repo restic.BlobSaver, nodes map[string]Node, inode u
|
|||
mode = 0644
|
||||
}
|
||||
err := tree.Insert(&restic.Node{
|
||||
Type: "file",
|
||||
Mode: mode,
|
||||
ModTime: node.ModTime,
|
||||
Name: name,
|
||||
UID: uint32(os.Getuid()),
|
||||
GID: uint32(os.Getgid()),
|
||||
Content: fc,
|
||||
Size: uint64(len(n.(File).Data)),
|
||||
Inode: fi,
|
||||
Links: lc,
|
||||
Type: "file",
|
||||
Mode: mode,
|
||||
ModTime: node.ModTime,
|
||||
Name: name,
|
||||
UID: uint32(os.Getuid()),
|
||||
GID: uint32(os.Getgid()),
|
||||
Content: fc,
|
||||
Size: uint64(len(n.(File).Data)),
|
||||
Inode: fi,
|
||||
Links: lc,
|
||||
GenericAttributes: getGenericAttributes(node.attributes, false),
|
||||
})
|
||||
rtest.OK(t, err)
|
||||
case Dir:
|
||||
id := saveDir(t, repo, node.Nodes, inode)
|
||||
id := saveDir(t, repo, node.Nodes, inode, getGenericAttributes)
|
||||
|
||||
mode := node.Mode
|
||||
if mode == 0 {
|
||||
|
@ -99,13 +111,14 @@ func saveDir(t testing.TB, repo restic.BlobSaver, nodes map[string]Node, inode u
|
|||
}
|
||||
|
||||
err := tree.Insert(&restic.Node{
|
||||
Type: "dir",
|
||||
Mode: mode,
|
||||
ModTime: node.ModTime,
|
||||
Name: name,
|
||||
UID: uint32(os.Getuid()),
|
||||
GID: uint32(os.Getgid()),
|
||||
Subtree: &id,
|
||||
Type: "dir",
|
||||
Mode: mode,
|
||||
ModTime: node.ModTime,
|
||||
Name: name,
|
||||
UID: uint32(os.Getuid()),
|
||||
GID: uint32(os.Getgid()),
|
||||
Subtree: &id,
|
||||
GenericAttributes: getGenericAttributes(node.attributes, false),
|
||||
})
|
||||
rtest.OK(t, err)
|
||||
default:
|
||||
|
@ -121,13 +134,13 @@ func saveDir(t testing.TB, repo restic.BlobSaver, nodes map[string]Node, inode u
|
|||
return id
|
||||
}
|
||||
|
||||
func saveSnapshot(t testing.TB, repo restic.Repository, snapshot Snapshot) (*restic.Snapshot, restic.ID) {
|
||||
func saveSnapshot(t testing.TB, repo restic.Repository, snapshot Snapshot, getGenericAttributes func(attr *FileAttributes, isDir bool) (genericAttributes map[restic.GenericAttributeType]json.RawMessage)) (*restic.Snapshot, restic.ID) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
wg, wgCtx := errgroup.WithContext(ctx)
|
||||
repo.StartPackUploader(wgCtx, wg)
|
||||
treeID := saveDir(t, repo, snapshot.Nodes, 1000)
|
||||
treeID := saveDir(t, repo, snapshot.Nodes, 1000, getGenericAttributes)
|
||||
err := repo.Flush(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -147,6 +160,11 @@ func saveSnapshot(t testing.TB, repo restic.Repository, snapshot Snapshot) (*res
|
|||
return sn, id
|
||||
}
|
||||
|
||||
var noopGetGenericAttributes = func(attr *FileAttributes, isDir bool) (genericAttributes map[restic.GenericAttributeType]json.RawMessage) {
|
||||
// No-op
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestRestorer(t *testing.T) {
|
||||
var tests = []struct {
|
||||
Snapshot
|
||||
|
@ -322,7 +340,7 @@ func TestRestorer(t *testing.T) {
|
|||
for _, test := range tests {
|
||||
t.Run("", func(t *testing.T) {
|
||||
repo := repository.TestRepository(t)
|
||||
sn, id := saveSnapshot(t, repo, test.Snapshot)
|
||||
sn, id := saveSnapshot(t, repo, test.Snapshot, noopGetGenericAttributes)
|
||||
t.Logf("snapshot saved as %v", id.Str())
|
||||
|
||||
res := NewRestorer(repo, sn, false, nil)
|
||||
|
@ -439,7 +457,7 @@ func TestRestorerRelative(t *testing.T) {
|
|||
t.Run("", func(t *testing.T) {
|
||||
repo := repository.TestRepository(t)
|
||||
|
||||
sn, id := saveSnapshot(t, repo, test.Snapshot)
|
||||
sn, id := saveSnapshot(t, repo, test.Snapshot, noopGetGenericAttributes)
|
||||
t.Logf("snapshot saved as %v", id.Str())
|
||||
|
||||
res := NewRestorer(repo, sn, false, nil)
|
||||
|
@ -669,7 +687,7 @@ func TestRestorerTraverseTree(t *testing.T) {
|
|||
for _, test := range tests {
|
||||
t.Run("", func(t *testing.T) {
|
||||
repo := repository.TestRepository(t)
|
||||
sn, _ := saveSnapshot(t, repo, test.Snapshot)
|
||||
sn, _ := saveSnapshot(t, repo, test.Snapshot, noopGetGenericAttributes)
|
||||
|
||||
res := NewRestorer(repo, sn, false, nil)
|
||||
|
||||
|
@ -745,7 +763,7 @@ func TestRestorerConsistentTimestampsAndPermissions(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}, noopGetGenericAttributes)
|
||||
|
||||
res := NewRestorer(repo, sn, false, nil)
|
||||
|
||||
|
@ -800,7 +818,7 @@ func TestVerifyCancel(t *testing.T) {
|
|||
}
|
||||
|
||||
repo := repository.TestRepository(t)
|
||||
sn, _ := saveSnapshot(t, repo, snapshot)
|
||||
sn, _ := saveSnapshot(t, repo, snapshot, noopGetGenericAttributes)
|
||||
|
||||
res := NewRestorer(repo, sn, false, nil)
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ func TestRestorerRestoreEmptyHardlinkedFileds(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}, noopGetGenericAttributes)
|
||||
|
||||
res := NewRestorer(repo, sn, false, nil)
|
||||
|
||||
|
@ -95,7 +95,7 @@ func TestRestorerProgressBar(t *testing.T) {
|
|||
},
|
||||
"file2": File{Links: 1, Inode: 2, Data: "example"},
|
||||
},
|
||||
})
|
||||
}, noopGetGenericAttributes)
|
||||
|
||||
mock := &printerMock{}
|
||||
progress := restoreui.NewProgress(mock, 0)
|
||||
|
|
|
@ -4,11 +4,20 @@
|
|||
package restorer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"math"
|
||||
"os"
|
||||
"path"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/restic/restic/internal/errors"
|
||||
"github.com/restic/restic/internal/repository"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
"github.com/restic/restic/internal/test"
|
||||
rtest "github.com/restic/restic/internal/test"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
@ -33,3 +42,500 @@ func getBlockCount(t *testing.T, filename string) int64 {
|
|||
|
||||
return int64(math.Ceil(float64(result) / 512))
|
||||
}
|
||||
|
||||
type DataStreamInfo struct {
|
||||
name string
|
||||
data string
|
||||
}
|
||||
|
||||
type NodeInfo struct {
|
||||
DataStreamInfo
|
||||
parentDir string
|
||||
attributes FileAttributes
|
||||
Exists bool
|
||||
IsDirectory bool
|
||||
}
|
||||
|
||||
func TestFileAttributeCombination(t *testing.T) {
|
||||
testFileAttributeCombination(t, false)
|
||||
}
|
||||
|
||||
func TestEmptyFileAttributeCombination(t *testing.T) {
|
||||
testFileAttributeCombination(t, true)
|
||||
}
|
||||
|
||||
func testFileAttributeCombination(t *testing.T, isEmpty bool) {
|
||||
t.Parallel()
|
||||
//Generate combination of 5 attributes.
|
||||
attributeCombinations := generateCombinations(5, []bool{})
|
||||
|
||||
fileName := "TestFile.txt"
|
||||
// Iterate through each attribute combination
|
||||
for _, attr1 := range attributeCombinations {
|
||||
|
||||
//Set up the required file information
|
||||
fileInfo := NodeInfo{
|
||||
DataStreamInfo: getDataStreamInfo(isEmpty, fileName),
|
||||
parentDir: "dir",
|
||||
attributes: getFileAttributes(attr1),
|
||||
Exists: false,
|
||||
}
|
||||
|
||||
//Get the current test name
|
||||
testName := getCombinationTestName(fileInfo, fileName, fileInfo.attributes)
|
||||
|
||||
//Run test
|
||||
t.Run(testName, func(t *testing.T) {
|
||||
mainFilePath := runAttributeTests(t, fileInfo, fileInfo.attributes)
|
||||
|
||||
verifyFileRestores(isEmpty, mainFilePath, t, fileInfo)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func generateCombinations(n int, prefix []bool) [][]bool {
|
||||
if n == 0 {
|
||||
// Return a slice containing the current permutation
|
||||
return [][]bool{append([]bool{}, prefix...)}
|
||||
}
|
||||
|
||||
// Generate combinations with True
|
||||
prefixTrue := append(prefix, true)
|
||||
permsTrue := generateCombinations(n-1, prefixTrue)
|
||||
|
||||
// Generate combinations with False
|
||||
prefixFalse := append(prefix, false)
|
||||
permsFalse := generateCombinations(n-1, prefixFalse)
|
||||
|
||||
// Combine combinations with True and False
|
||||
return append(permsTrue, permsFalse...)
|
||||
}
|
||||
|
||||
func getDataStreamInfo(isEmpty bool, fileName string) DataStreamInfo {
|
||||
var dataStreamInfo DataStreamInfo
|
||||
if isEmpty {
|
||||
dataStreamInfo = DataStreamInfo{
|
||||
name: fileName,
|
||||
}
|
||||
} else {
|
||||
dataStreamInfo = DataStreamInfo{
|
||||
name: fileName,
|
||||
data: "Main file data stream.",
|
||||
}
|
||||
}
|
||||
return dataStreamInfo
|
||||
}
|
||||
|
||||
func getFileAttributes(values []bool) FileAttributes {
|
||||
return FileAttributes{
|
||||
ReadOnly: values[0],
|
||||
Hidden: values[1],
|
||||
System: values[2],
|
||||
Archive: values[3],
|
||||
Encrypted: values[4],
|
||||
}
|
||||
}
|
||||
|
||||
func getCombinationTestName(fi NodeInfo, fileName string, overwriteAttr FileAttributes) string {
|
||||
if fi.attributes.ReadOnly {
|
||||
fileName += "-ReadOnly"
|
||||
}
|
||||
if fi.attributes.Hidden {
|
||||
fileName += "-Hidden"
|
||||
}
|
||||
if fi.attributes.System {
|
||||
fileName += "-System"
|
||||
}
|
||||
if fi.attributes.Archive {
|
||||
fileName += "-Archive"
|
||||
}
|
||||
if fi.attributes.Encrypted {
|
||||
fileName += "-Encrypted"
|
||||
}
|
||||
if fi.Exists {
|
||||
fileName += "-Overwrite"
|
||||
if overwriteAttr.ReadOnly {
|
||||
fileName += "-R"
|
||||
}
|
||||
if overwriteAttr.Hidden {
|
||||
fileName += "-H"
|
||||
}
|
||||
if overwriteAttr.System {
|
||||
fileName += "-S"
|
||||
}
|
||||
if overwriteAttr.Archive {
|
||||
fileName += "-A"
|
||||
}
|
||||
if overwriteAttr.Encrypted {
|
||||
fileName += "-E"
|
||||
}
|
||||
}
|
||||
return fileName
|
||||
}
|
||||
|
||||
func runAttributeTests(t *testing.T, fileInfo NodeInfo, existingFileAttr FileAttributes) string {
|
||||
testDir := t.TempDir()
|
||||
res, _ := setupWithFileAttributes(t, fileInfo, testDir, existingFileAttr)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
err := res.RestoreTo(ctx, testDir)
|
||||
rtest.OK(t, err)
|
||||
|
||||
mainFilePath := path.Join(testDir, fileInfo.parentDir, fileInfo.name)
|
||||
//Verify restore
|
||||
verifyFileAttributes(t, mainFilePath, fileInfo.attributes)
|
||||
return mainFilePath
|
||||
}
|
||||
|
||||
func setupWithFileAttributes(t *testing.T, nodeInfo NodeInfo, testDir string, existingFileAttr FileAttributes) (*Restorer, []int) {
|
||||
t.Helper()
|
||||
if nodeInfo.Exists {
|
||||
if !nodeInfo.IsDirectory {
|
||||
err := os.MkdirAll(path.Join(testDir, nodeInfo.parentDir), os.ModeDir)
|
||||
rtest.OK(t, err)
|
||||
filepath := path.Join(testDir, nodeInfo.parentDir, nodeInfo.name)
|
||||
if existingFileAttr.Encrypted {
|
||||
err := createEncryptedFileWriteData(filepath, nodeInfo)
|
||||
rtest.OK(t, err)
|
||||
} else {
|
||||
// Write the data to the file
|
||||
file, err := os.OpenFile(path.Clean(filepath), os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600)
|
||||
rtest.OK(t, err)
|
||||
_, err = file.Write([]byte(nodeInfo.data))
|
||||
rtest.OK(t, err)
|
||||
|
||||
err = file.Close()
|
||||
rtest.OK(t, err)
|
||||
}
|
||||
} else {
|
||||
err := os.MkdirAll(path.Join(testDir, nodeInfo.parentDir, nodeInfo.name), os.ModeDir)
|
||||
rtest.OK(t, err)
|
||||
}
|
||||
|
||||
pathPointer, err := syscall.UTF16PtrFromString(path.Join(testDir, nodeInfo.parentDir, nodeInfo.name))
|
||||
rtest.OK(t, err)
|
||||
syscall.SetFileAttributes(pathPointer, getAttributeValue(&existingFileAttr))
|
||||
}
|
||||
|
||||
index := 0
|
||||
|
||||
order := []int{}
|
||||
streams := []DataStreamInfo{}
|
||||
if !nodeInfo.IsDirectory {
|
||||
order = append(order, index)
|
||||
index++
|
||||
streams = append(streams, nodeInfo.DataStreamInfo)
|
||||
}
|
||||
return setup(t, getNodes(nodeInfo.parentDir, nodeInfo.name, order, streams, nodeInfo.IsDirectory, &nodeInfo.attributes)), order
|
||||
}
|
||||
|
||||
func createEncryptedFileWriteData(filepath string, fileInfo NodeInfo) (err error) {
|
||||
var ptr *uint16
|
||||
if ptr, err = windows.UTF16PtrFromString(filepath); err != nil {
|
||||
return err
|
||||
}
|
||||
var handle windows.Handle
|
||||
//Create the file with encrypted flag
|
||||
if handle, err = windows.CreateFile(ptr, uint32(windows.GENERIC_READ|windows.GENERIC_WRITE), uint32(windows.FILE_SHARE_READ), nil, uint32(windows.CREATE_ALWAYS), windows.FILE_ATTRIBUTE_ENCRYPTED, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
//Write data to file
|
||||
if _, err = windows.Write(handle, []byte(fileInfo.data)); err != nil {
|
||||
return err
|
||||
}
|
||||
//Close handle
|
||||
return windows.CloseHandle(handle)
|
||||
}
|
||||
|
||||
func setup(t *testing.T, nodesMap map[string]Node) *Restorer {
|
||||
repo := repository.TestRepository(t)
|
||||
getFileAttributes := func(attr *FileAttributes, isDir bool) (genericAttributes map[restic.GenericAttributeType]json.RawMessage) {
|
||||
if attr == nil {
|
||||
return
|
||||
}
|
||||
|
||||
fileattr := getAttributeValue(attr)
|
||||
|
||||
if isDir {
|
||||
//If the node is a directory add FILE_ATTRIBUTE_DIRECTORY to attributes
|
||||
fileattr |= windows.FILE_ATTRIBUTE_DIRECTORY
|
||||
}
|
||||
attrs, err := restic.WindowsAttrsToGenericAttributes(restic.WindowsAttributes{FileAttributes: &fileattr})
|
||||
test.OK(t, err)
|
||||
return attrs
|
||||
}
|
||||
sn, _ := saveSnapshot(t, repo, Snapshot{
|
||||
Nodes: nodesMap,
|
||||
}, getFileAttributes)
|
||||
res := NewRestorer(repo, sn, false, nil)
|
||||
return res
|
||||
}
|
||||
|
||||
func getAttributeValue(attr *FileAttributes) uint32 {
|
||||
var fileattr uint32
|
||||
if attr.ReadOnly {
|
||||
fileattr |= windows.FILE_ATTRIBUTE_READONLY
|
||||
}
|
||||
if attr.Hidden {
|
||||
fileattr |= windows.FILE_ATTRIBUTE_HIDDEN
|
||||
}
|
||||
if attr.Encrypted {
|
||||
fileattr |= windows.FILE_ATTRIBUTE_ENCRYPTED
|
||||
}
|
||||
if attr.Archive {
|
||||
fileattr |= windows.FILE_ATTRIBUTE_ARCHIVE
|
||||
}
|
||||
if attr.System {
|
||||
fileattr |= windows.FILE_ATTRIBUTE_SYSTEM
|
||||
}
|
||||
return fileattr
|
||||
}
|
||||
|
||||
func getNodes(dir string, mainNodeName string, order []int, streams []DataStreamInfo, isDirectory bool, attributes *FileAttributes) map[string]Node {
|
||||
var mode os.FileMode
|
||||
if isDirectory {
|
||||
mode = os.FileMode(2147484159)
|
||||
} else {
|
||||
if attributes != nil && attributes.ReadOnly {
|
||||
mode = os.FileMode(0o444)
|
||||
} else {
|
||||
mode = os.FileMode(0o666)
|
||||
}
|
||||
}
|
||||
|
||||
getFileNodes := func() map[string]Node {
|
||||
nodes := map[string]Node{}
|
||||
if isDirectory {
|
||||
//Add a directory node at the same level as the other streams
|
||||
nodes[mainNodeName] = Dir{
|
||||
ModTime: time.Now(),
|
||||
attributes: attributes,
|
||||
Mode: mode,
|
||||
}
|
||||
}
|
||||
|
||||
if len(streams) > 0 {
|
||||
for _, index := range order {
|
||||
stream := streams[index]
|
||||
|
||||
var attr *FileAttributes = nil
|
||||
if mainNodeName == stream.name {
|
||||
attr = attributes
|
||||
} else if attributes != nil && attributes.Encrypted {
|
||||
//Set encrypted attribute
|
||||
attr = &FileAttributes{Encrypted: true}
|
||||
}
|
||||
|
||||
nodes[stream.name] = File{
|
||||
ModTime: time.Now(),
|
||||
Data: stream.data,
|
||||
Mode: mode,
|
||||
attributes: attr,
|
||||
}
|
||||
}
|
||||
}
|
||||
return nodes
|
||||
}
|
||||
|
||||
return map[string]Node{
|
||||
dir: Dir{
|
||||
Mode: normalizeFileMode(0750 | mode),
|
||||
ModTime: time.Now(),
|
||||
Nodes: getFileNodes(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func verifyFileAttributes(t *testing.T, mainFilePath string, attr FileAttributes) {
|
||||
ptr, err := windows.UTF16PtrFromString(mainFilePath)
|
||||
rtest.OK(t, err)
|
||||
//Get file attributes using syscall
|
||||
fileAttributes, err := syscall.GetFileAttributes(ptr)
|
||||
rtest.OK(t, err)
|
||||
//Test positive and negative scenarios
|
||||
if attr.ReadOnly {
|
||||
rtest.Assert(t, fileAttributes&windows.FILE_ATTRIBUTE_READONLY != 0, "Expected read only attibute.")
|
||||
} else {
|
||||
rtest.Assert(t, fileAttributes&windows.FILE_ATTRIBUTE_READONLY == 0, "Unexpected read only attibute.")
|
||||
}
|
||||
if attr.Hidden {
|
||||
rtest.Assert(t, fileAttributes&windows.FILE_ATTRIBUTE_HIDDEN != 0, "Expected hidden attibute.")
|
||||
} else {
|
||||
rtest.Assert(t, fileAttributes&windows.FILE_ATTRIBUTE_HIDDEN == 0, "Unexpected hidden attibute.")
|
||||
}
|
||||
if attr.System {
|
||||
rtest.Assert(t, fileAttributes&windows.FILE_ATTRIBUTE_SYSTEM != 0, "Expected system attibute.")
|
||||
} else {
|
||||
rtest.Assert(t, fileAttributes&windows.FILE_ATTRIBUTE_SYSTEM == 0, "Unexpected system attibute.")
|
||||
}
|
||||
if attr.Archive {
|
||||
rtest.Assert(t, fileAttributes&windows.FILE_ATTRIBUTE_ARCHIVE != 0, "Expected archive attibute.")
|
||||
} else {
|
||||
rtest.Assert(t, fileAttributes&windows.FILE_ATTRIBUTE_ARCHIVE == 0, "Unexpected archive attibute.")
|
||||
}
|
||||
if attr.Encrypted {
|
||||
rtest.Assert(t, fileAttributes&windows.FILE_ATTRIBUTE_ENCRYPTED != 0, "Expected encrypted attibute.")
|
||||
} else {
|
||||
rtest.Assert(t, fileAttributes&windows.FILE_ATTRIBUTE_ENCRYPTED == 0, "Unexpected encrypted attibute.")
|
||||
}
|
||||
}
|
||||
|
||||
func verifyFileRestores(isEmpty bool, mainFilePath string, t *testing.T, fileInfo NodeInfo) {
|
||||
if isEmpty {
|
||||
_, err1 := os.Stat(mainFilePath)
|
||||
rtest.Assert(t, !errors.Is(err1, os.ErrNotExist), "The file "+fileInfo.name+" does not exist")
|
||||
} else {
|
||||
|
||||
verifyMainFileRestore(t, mainFilePath, fileInfo)
|
||||
}
|
||||
}
|
||||
|
||||
func verifyMainFileRestore(t *testing.T, mainFilePath string, fileInfo NodeInfo) {
|
||||
fi, err1 := os.Stat(mainFilePath)
|
||||
rtest.Assert(t, !errors.Is(err1, os.ErrNotExist), "The file "+fileInfo.name+" does not exist")
|
||||
|
||||
size := fi.Size()
|
||||
rtest.Assert(t, size > 0, "The file "+fileInfo.name+" exists but is empty")
|
||||
|
||||
content, err := os.ReadFile(mainFilePath)
|
||||
rtest.OK(t, err)
|
||||
rtest.Assert(t, string(content) == fileInfo.data, "The file "+fileInfo.name+" exists but the content is not overwritten")
|
||||
}
|
||||
|
||||
func TestDirAttributeCombination(t *testing.T) {
|
||||
t.Parallel()
|
||||
attributeCombinations := generateCombinations(4, []bool{})
|
||||
|
||||
dirName := "TestDir"
|
||||
// Iterate through each attribute combination
|
||||
for _, attr1 := range attributeCombinations {
|
||||
|
||||
//Set up the required directory information
|
||||
dirInfo := NodeInfo{
|
||||
DataStreamInfo: DataStreamInfo{
|
||||
name: dirName,
|
||||
},
|
||||
parentDir: "dir",
|
||||
attributes: getDirFileAttributes(attr1),
|
||||
Exists: false,
|
||||
IsDirectory: true,
|
||||
}
|
||||
|
||||
//Get the current test name
|
||||
testName := getCombinationTestName(dirInfo, dirName, dirInfo.attributes)
|
||||
|
||||
//Run test
|
||||
t.Run(testName, func(t *testing.T) {
|
||||
mainDirPath := runAttributeTests(t, dirInfo, dirInfo.attributes)
|
||||
|
||||
//Check directory exists
|
||||
_, err1 := os.Stat(mainDirPath)
|
||||
rtest.Assert(t, !errors.Is(err1, os.ErrNotExist), "The directory "+dirInfo.name+" does not exist")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func getDirFileAttributes(values []bool) FileAttributes {
|
||||
return FileAttributes{
|
||||
// readonly not valid for directories
|
||||
Hidden: values[0],
|
||||
System: values[1],
|
||||
Archive: values[2],
|
||||
Encrypted: values[3],
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileAttributeCombinationsOverwrite(t *testing.T) {
|
||||
testFileAttributeCombinationsOverwrite(t, false)
|
||||
}
|
||||
|
||||
func TestEmptyFileAttributeCombinationsOverwrite(t *testing.T) {
|
||||
testFileAttributeCombinationsOverwrite(t, true)
|
||||
}
|
||||
|
||||
func testFileAttributeCombinationsOverwrite(t *testing.T, isEmpty bool) {
|
||||
t.Parallel()
|
||||
//Get attribute combinations
|
||||
attributeCombinations := generateCombinations(5, []bool{})
|
||||
//Get overwrite file attribute combinations
|
||||
overwriteCombinations := generateCombinations(5, []bool{})
|
||||
|
||||
fileName := "TestOverwriteFile"
|
||||
|
||||
//Iterate through each attribute combination
|
||||
for _, attr1 := range attributeCombinations {
|
||||
|
||||
fileInfo := NodeInfo{
|
||||
DataStreamInfo: getDataStreamInfo(isEmpty, fileName),
|
||||
parentDir: "dir",
|
||||
attributes: getFileAttributes(attr1),
|
||||
Exists: true,
|
||||
}
|
||||
|
||||
overwriteFileAttributes := []FileAttributes{}
|
||||
|
||||
for _, overwrite := range overwriteCombinations {
|
||||
overwriteFileAttributes = append(overwriteFileAttributes, getFileAttributes(overwrite))
|
||||
}
|
||||
|
||||
//Iterate through each overwrite attribute combination
|
||||
for _, overwriteFileAttr := range overwriteFileAttributes {
|
||||
//Get the test name
|
||||
testName := getCombinationTestName(fileInfo, fileName, overwriteFileAttr)
|
||||
|
||||
//Run test
|
||||
t.Run(testName, func(t *testing.T) {
|
||||
mainFilePath := runAttributeTests(t, fileInfo, overwriteFileAttr)
|
||||
|
||||
verifyFileRestores(isEmpty, mainFilePath, t, fileInfo)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDirAttributeCombinationsOverwrite(t *testing.T) {
|
||||
t.Parallel()
|
||||
//Get attribute combinations
|
||||
attributeCombinations := generateCombinations(4, []bool{})
|
||||
//Get overwrite dir attribute combinations
|
||||
overwriteCombinations := generateCombinations(4, []bool{})
|
||||
|
||||
dirName := "TestOverwriteDir"
|
||||
|
||||
//Iterate through each attribute combination
|
||||
for _, attr1 := range attributeCombinations {
|
||||
|
||||
dirInfo := NodeInfo{
|
||||
DataStreamInfo: DataStreamInfo{
|
||||
name: dirName,
|
||||
},
|
||||
parentDir: "dir",
|
||||
attributes: getDirFileAttributes(attr1),
|
||||
Exists: true,
|
||||
IsDirectory: true,
|
||||
}
|
||||
|
||||
overwriteDirFileAttributes := []FileAttributes{}
|
||||
|
||||
for _, overwrite := range overwriteCombinations {
|
||||
overwriteDirFileAttributes = append(overwriteDirFileAttributes, getDirFileAttributes(overwrite))
|
||||
}
|
||||
|
||||
//Iterate through each overwrite attribute combinations
|
||||
for _, overwriteDirAttr := range overwriteDirFileAttributes {
|
||||
//Get the test name
|
||||
testName := getCombinationTestName(dirInfo, dirName, overwriteDirAttr)
|
||||
|
||||
//Run test
|
||||
t.Run(testName, func(t *testing.T) {
|
||||
mainDirPath := runAttributeTests(t, dirInfo, dirInfo.attributes)
|
||||
|
||||
//Check directory exists
|
||||
_, err1 := os.Stat(mainDirPath)
|
||||
rtest.Assert(t, !errors.Is(err1, os.ErrNotExist), "The directory "+dirInfo.name+" does not exist")
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue