From 4fcedb4bae9c861eb0cdbf738ca5a05c2f17e1df Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Fri, 30 Aug 2024 11:25:51 +0200 Subject: [PATCH] backup: support specifying volume instead of path on Windows "C:" (volume name) versus "C:\" (path) --- changelog/unreleased/issue-2004 | 19 ++++++++++ internal/archiver/archiver.go | 7 +++- internal/archiver/archiver_test.go | 60 ++++++++++++++++++++++++++++++ 3 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 changelog/unreleased/issue-2004 diff --git a/changelog/unreleased/issue-2004 b/changelog/unreleased/issue-2004 new file mode 100644 index 000000000..45bc07ca8 --- /dev/null +++ b/changelog/unreleased/issue-2004 @@ -0,0 +1,19 @@ +Bugfix: Correctly handle passing volume name to `backup` command + +On Windows, when the specified backup target only included the volume +name without a trailing slash, for example, `C:`, then restoring the +resulting snapshot would result in an error. Note that using `C:\` +as backup target worked correctly. + +Specifying volume names now works correctly. + +To restore snapshots created before this bugfix, use the `:` +syntax. For a snapshot with ID `12345678` and a backup of `C:`, the following +command can be used: + +``` +restic restore 12345678:/C/C:./ --target output/folder +``` + +https://github.com/restic/restic/issues/2004 +https://github.com/restic/restic/pull/5028 diff --git a/internal/archiver/archiver.go b/internal/archiver/archiver.go index e44151298..e7c346d3a 100644 --- a/internal/archiver/archiver.go +++ b/internal/archiver/archiver.go @@ -715,7 +715,12 @@ func resolveRelativeTargets(filesys fs.FS, targets []string) ([]string, error) { debug.Log("targets before resolving: %v", targets) result := make([]string, 0, len(targets)) for _, target := range targets { - target = filesys.Clean(target) + if target != "" && filesys.VolumeName(target) == target { + // special case to allow users to also specify a volume name "C:" instead of a path "C:\" + target = target + filesys.Separator() + } else { + target = filesys.Clean(target) + } pc, _ := pathComponents(filesys, target, false) if len(pc) > 0 { result = append(result, target) diff --git a/internal/archiver/archiver_test.go b/internal/archiver/archiver_test.go index b519387db..c54f9ea33 100644 --- a/internal/archiver/archiver_test.go +++ b/internal/archiver/archiver_test.go @@ -1448,6 +1448,66 @@ func TestArchiverSnapshot(t *testing.T) { } } +func TestResolveRelativeTargetsSpecial(t *testing.T) { + var tests = []struct { + name string + targets []string + expected []string + win bool + }{ + { + name: "basic relative path", + targets: []string{filepath.FromSlash("some/path")}, + expected: []string{filepath.FromSlash("some/path")}, + }, + { + name: "partial relative path", + targets: []string{filepath.FromSlash("../some/path")}, + expected: []string{filepath.FromSlash("../some/path")}, + }, + { + name: "basic absolute path", + targets: []string{filepath.FromSlash("/some/path")}, + expected: []string{filepath.FromSlash("/some/path")}, + }, + { + name: "volume name", + targets: []string{"C:"}, + expected: []string{"C:\\"}, + win: true, + }, + { + name: "volume root path", + targets: []string{"C:\\"}, + expected: []string{"C:\\"}, + win: true, + }, + { + name: "UNC path", + targets: []string{"\\\\server\\volume"}, + expected: []string{"\\\\server\\volume\\"}, + win: true, + }, + { + name: "UNC path with trailing slash", + targets: []string{"\\\\server\\volume\\"}, + expected: []string{"\\\\server\\volume\\"}, + win: true, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + if test.win && runtime.GOOS != "windows" { + t.Skip("skip test on unix") + } + + targets, err := resolveRelativeTargets(&fs.Local{}, test.targets) + rtest.OK(t, err) + rtest.Equals(t, test.expected, targets) + }) + } +} + func TestArchiverSnapshotSelect(t *testing.T) { var tests = []struct { name string