2020-11-06 19:36:48 +00:00
|
|
|
// +build windows
|
|
|
|
|
|
|
|
package fs
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"regexp"
|
|
|
|
"strings"
|
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
2021-04-30 15:01:40 +00:00
|
|
|
ole "github.com/go-ole/go-ole"
|
2020-11-06 19:36:48 +00:00
|
|
|
"github.com/restic/restic/internal/options"
|
|
|
|
)
|
|
|
|
|
|
|
|
func matchStrings(ptrs []string, strs []string) bool {
|
|
|
|
if len(ptrs) != len(strs) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
for i, p := range ptrs {
|
2021-04-30 15:01:40 +00:00
|
|
|
if p == "" {
|
|
|
|
return false
|
|
|
|
}
|
2020-11-06 19:36:48 +00:00
|
|
|
matched, err := regexp.MatchString(p, strs[i])
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
if !matched {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
func matchMap(strs []string, m map[string]struct{}) bool {
|
|
|
|
if len(strs) != len(m) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, s := range strs {
|
|
|
|
if _, ok := m[s]; !ok {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestVSSConfig(t *testing.T) {
|
|
|
|
type config struct {
|
|
|
|
excludeAllMountPoints bool
|
|
|
|
timeout time.Duration
|
2021-04-30 15:01:40 +00:00
|
|
|
provider string
|
2020-11-06 19:36:48 +00:00
|
|
|
}
|
|
|
|
setTests := []struct {
|
|
|
|
input options.Options
|
|
|
|
output config
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
options.Options{
|
2021-04-30 15:01:40 +00:00
|
|
|
"vss.timeout": "6h38m42s",
|
|
|
|
"vss.provider": "Ms",
|
2020-11-06 19:36:48 +00:00
|
|
|
},
|
|
|
|
config{
|
2021-04-30 15:01:40 +00:00
|
|
|
timeout: 23922000000000,
|
|
|
|
provider: "Ms",
|
2020-11-06 19:36:48 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
options.Options{
|
2024-04-28 22:21:33 +00:00
|
|
|
"vss.exclude-all-mount-points": "t",
|
|
|
|
"vss.provider": "{b5946137-7b9f-4925-af80-51abd60b20d5}",
|
2020-11-06 19:36:48 +00:00
|
|
|
},
|
|
|
|
config{
|
|
|
|
excludeAllMountPoints: true,
|
|
|
|
timeout: 120000000000,
|
2021-04-30 15:01:40 +00:00
|
|
|
provider: "{b5946137-7b9f-4925-af80-51abd60b20d5}",
|
2020-11-06 19:36:48 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
options.Options{
|
2024-04-28 22:21:33 +00:00
|
|
|
"vss.exclude-all-mount-points": "0",
|
|
|
|
"vss.exclude-volumes": "",
|
|
|
|
"vss.timeout": "120s",
|
|
|
|
"vss.provider": "Microsoft Software Shadow Copy provider 1.0",
|
2020-11-06 19:36:48 +00:00
|
|
|
},
|
|
|
|
config{
|
2021-04-30 15:01:40 +00:00
|
|
|
timeout: 120000000000,
|
|
|
|
provider: "Microsoft Software Shadow Copy provider 1.0",
|
2020-11-06 19:36:48 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
for i, test := range setTests {
|
|
|
|
t.Run(fmt.Sprintf("test-%d", i), func(t *testing.T) {
|
|
|
|
cfg, err := ParseVSSConfig(test.input)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2020-11-10 03:48:05 +00:00
|
|
|
errorHandler := func(item string, err error) {
|
2020-11-06 19:36:48 +00:00
|
|
|
t.Fatalf("unexpected error (%v)", err)
|
|
|
|
}
|
|
|
|
messageHandler := func(msg string, args ...interface{}) {
|
|
|
|
t.Fatalf("unexpected message (%s)", fmt.Sprintf(msg, args))
|
|
|
|
}
|
|
|
|
|
|
|
|
dst := NewLocalVss(errorHandler, messageHandler, cfg)
|
|
|
|
|
|
|
|
if dst.excludeAllMountPoints != test.output.excludeAllMountPoints ||
|
2021-04-30 15:01:40 +00:00
|
|
|
dst.excludeVolumes != nil || dst.timeout != test.output.timeout ||
|
|
|
|
dst.provider != test.output.provider {
|
2020-11-06 19:36:48 +00:00
|
|
|
t.Fatalf("wrong result, want:\n %#v\ngot:\n %#v", test.output, dst)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestParseMountPoints(t *testing.T) {
|
|
|
|
volumeMatch := regexp.MustCompile(`^\\\\\?\\Volume\{[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}\}\\$`)
|
|
|
|
|
|
|
|
// It's not a good idea to test functions based on GetVolumeNameForVolumeMountPoint by calling
|
|
|
|
// GetVolumeNameForVolumeMountPoint itself, but we have restricted test environment:
|
|
|
|
// cannot manage volumes and can only be sure that the mount point C:\ exists
|
|
|
|
sysVolume, err := GetVolumeNameForVolumeMountPoint("C:")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2024-04-28 22:18:46 +00:00
|
|
|
// We don't know a valid volume GUID path for c:\, but we'll at least check its format
|
2020-11-06 19:36:48 +00:00
|
|
|
if !volumeMatch.MatchString(sysVolume) {
|
|
|
|
t.Fatalf("invalid volume GUID path: %s", sysVolume)
|
|
|
|
}
|
2024-04-28 22:18:46 +00:00
|
|
|
// Changing the case and removing trailing backslash allows tests
|
|
|
|
// the equality of different ways of writing a volume name
|
2020-11-06 19:36:48 +00:00
|
|
|
sysVolumeMutated := strings.ToUpper(sysVolume[:len(sysVolume)-1])
|
|
|
|
sysVolumeMatch := strings.ToLower(sysVolume)
|
|
|
|
|
|
|
|
type check struct {
|
|
|
|
volume string
|
|
|
|
result bool
|
|
|
|
}
|
|
|
|
setTests := []struct {
|
|
|
|
input options.Options
|
|
|
|
output []string
|
|
|
|
checks []check
|
|
|
|
errors []string
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
options.Options{
|
2024-04-28 22:21:33 +00:00
|
|
|
"vss.exclude-volumes": `c:;c:\;` + sysVolume + `;` + sysVolumeMutated,
|
2020-11-06 19:36:48 +00:00
|
|
|
},
|
|
|
|
[]string{
|
|
|
|
sysVolumeMatch,
|
|
|
|
},
|
|
|
|
[]check{
|
2024-04-28 22:23:50 +00:00
|
|
|
{`c:\`, false},
|
|
|
|
{`c:`, false},
|
|
|
|
{sysVolume, false},
|
|
|
|
{sysVolumeMutated, false},
|
2020-11-06 19:36:48 +00:00
|
|
|
},
|
|
|
|
[]string{},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
options.Options{
|
2024-04-28 22:21:33 +00:00
|
|
|
"vss.exclude-volumes": `z:\nonexistent;c:;c:\windows\;\\?\Volume{39b9cac2-bcdb-4d51-97c8-0d0677d607fb}\`,
|
2020-11-06 19:36:48 +00:00
|
|
|
},
|
|
|
|
[]string{
|
|
|
|
sysVolumeMatch,
|
|
|
|
},
|
|
|
|
[]check{
|
2024-04-28 22:23:50 +00:00
|
|
|
{`c:\windows\`, true},
|
|
|
|
{`\\?\Volume{39b9cac2-bcdb-4d51-97c8-0d0677d607fb}\`, true},
|
|
|
|
{`c:`, false},
|
|
|
|
{``, true},
|
2020-11-06 19:36:48 +00:00
|
|
|
},
|
|
|
|
[]string{
|
2024-04-28 22:21:33 +00:00
|
|
|
`failed to parse vss\.exclude-volumes \[z:\\nonexistent\]:.*`,
|
|
|
|
`failed to parse vss\.exclude-volumes \[c:\\windows\\\]:.*`,
|
|
|
|
`failed to parse vss\.exclude-volumes \[\\\\\?\\Volume\{39b9cac2-bcdb-4d51-97c8-0d0677d607fb\}\\\]:.*`,
|
2020-11-06 19:36:48 +00:00
|
|
|
`failed to get volume from mount point \[c:\\windows\\\]:.*`,
|
|
|
|
`failed to get volume from mount point \[\\\\\?\\Volume\{39b9cac2-bcdb-4d51-97c8-0d0677d607fb\}\\\]:.*`,
|
|
|
|
`failed to get volume from mount point \[\]:.*`,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for i, test := range setTests {
|
|
|
|
t.Run(fmt.Sprintf("test-%d", i), func(t *testing.T) {
|
|
|
|
cfg, err := ParseVSSConfig(test.input)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
var log []string
|
2020-11-10 03:48:05 +00:00
|
|
|
errorHandler := func(item string, err error) {
|
2020-11-06 19:36:48 +00:00
|
|
|
log = append(log, strings.TrimSpace(err.Error()))
|
|
|
|
}
|
|
|
|
messageHandler := func(msg string, args ...interface{}) {
|
|
|
|
t.Fatalf("unexpected message (%s)", fmt.Sprintf(msg, args))
|
|
|
|
}
|
|
|
|
|
|
|
|
dst := NewLocalVss(errorHandler, messageHandler, cfg)
|
|
|
|
|
|
|
|
if !matchMap(test.output, dst.excludeVolumes) {
|
|
|
|
t.Fatalf("wrong result, want:\n %#v\ngot:\n %#v",
|
|
|
|
test.output, dst.excludeVolumes)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, c := range test.checks {
|
2024-04-28 22:23:50 +00:00
|
|
|
if dst.isMountPointIncluded(c.volume) != c.result {
|
|
|
|
t.Fatalf(`wrong check: isMountPointIncluded("%s") != %v`, c.volume, c.result)
|
2020-11-06 19:36:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if !matchStrings(test.errors, log) {
|
|
|
|
t.Fatalf("wrong log, want:\n %#v\ngot:\n %#v", test.errors, log)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2021-04-30 15:01:40 +00:00
|
|
|
|
|
|
|
func TestParseProvider(t *testing.T) {
|
|
|
|
msProvider := ole.NewGUID("{b5946137-7b9f-4925-af80-51abd60b20d5}")
|
|
|
|
setTests := []struct {
|
|
|
|
provider string
|
|
|
|
id *ole.GUID
|
|
|
|
result string
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
"",
|
|
|
|
ole.IID_NULL,
|
|
|
|
"",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"mS",
|
|
|
|
msProvider,
|
|
|
|
"",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"{B5946137-7b9f-4925-Af80-51abD60b20d5}",
|
|
|
|
msProvider,
|
|
|
|
"",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"Microsoft Software Shadow Copy provider 1.0",
|
|
|
|
msProvider,
|
|
|
|
"",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"{04560982-3d7d-4bbc-84f7-0712f833a28f}",
|
|
|
|
nil,
|
|
|
|
`invalid VSS provider "{04560982-3d7d-4bbc-84f7-0712f833a28f}"`,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"non-existent provider",
|
|
|
|
nil,
|
|
|
|
`invalid VSS provider "non-existent provider"`,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
_ = ole.CoInitializeEx(0, ole.COINIT_MULTITHREADED)
|
|
|
|
|
|
|
|
for i, test := range setTests {
|
|
|
|
t.Run(fmt.Sprintf("test-%d", i), func(t *testing.T) {
|
|
|
|
id, err := getProviderID(test.provider)
|
|
|
|
|
|
|
|
if err != nil && id != nil {
|
|
|
|
t.Fatalf("err!=nil but id=%v", id)
|
|
|
|
}
|
|
|
|
|
|
|
|
if test.result != "" || err != nil {
|
|
|
|
var result string
|
|
|
|
if err != nil {
|
|
|
|
result = err.Error()
|
|
|
|
}
|
|
|
|
matched, err := regexp.MatchString(test.result, result)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
if !matched || test.result == "" {
|
|
|
|
t.Fatalf("wrong result, want:\n %#v\ngot:\n %#v", test.result, result)
|
|
|
|
}
|
|
|
|
} else if !ole.IsEqualGUID(id, test.id) {
|
|
|
|
t.Fatalf("wrong id, want:\n %s\ngot:\n %s", test.id.String(), id.String())
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|