diff --git a/internal/backend/local/local.go b/internal/backend/local/local.go index d2dedacbb..fd43332a8 100644 --- a/internal/backend/local/local.go +++ b/internal/backend/local/local.go @@ -57,11 +57,14 @@ func Open(cfg Config) (*Local, error) { // if data dir exists, make sure that all subdirs also exist datadir := be.Dirname(restic.Handle{Type: restic.DataFile}) if dirExists(datadir) { + debug.Log("datadir %v exists", datadir) for _, d := range be.Paths() { - if _, err := filepath.Rel(datadir, d); err != nil { + if !fs.HasPathPrefix(datadir, d) { + debug.Log("%v is not subdir of datadir %v", d, datadir) continue } + debug.Log("MkdirAll %v", d) err := fs.MkdirAll(d, backend.Modes.Dir) if err != nil { return nil, errors.Wrap(err, "MkdirAll") diff --git a/internal/backend/local/local_test.go b/internal/backend/local/local_test.go index 1d15f5ff9..e5b1e2533 100644 --- a/internal/backend/local/local_test.go +++ b/internal/backend/local/local_test.go @@ -104,6 +104,20 @@ func openclose(t testing.TB, dir string) { } } +func mkdir(t testing.TB, dir string) { + err := os.Mkdir(dir, 0700) + if err != nil { + t.Fatal(err) + } +} + +func removeAll(t testing.TB, dir string) { + err := os.RemoveAll(dir) + if err != nil { + t.Fatal(err) + } +} + func TestOpenNotExistingDirectory(t *testing.T) { dir, cleanup := TempDir(t) defer cleanup() @@ -114,4 +128,9 @@ func TestOpenNotExistingDirectory(t *testing.T) { openclose(t, dir) empty(t, dir) + + mkdir(t, filepath.Join(dir, "data")) + openclose(t, dir) + removeAll(t, filepath.Join(dir, "data")) + empty(t, dir) } diff --git a/internal/fs/path_prefix.go b/internal/fs/path_prefix.go new file mode 100644 index 000000000..d5398db12 --- /dev/null +++ b/internal/fs/path_prefix.go @@ -0,0 +1,41 @@ +package fs + +import ( + "path/filepath" +) + +// HasPathPrefix returns true if p is a subdir of (or a file within) base. It +// assumes a file system which is case sensitive. For relative paths, false is +// returned. +func HasPathPrefix(base, p string) bool { + if filepath.VolumeName(base) != filepath.VolumeName(p) { + return false + } + + if !filepath.IsAbs(base) || !filepath.IsAbs(p) { + return false + } + + base = filepath.Clean(base) + p = filepath.Clean(p) + + if base == p { + return true + } + + for { + dir := filepath.Dir(p) + + if base == dir { + return true + } + + if p == dir { + break + } + + p = dir + } + + return false +} diff --git a/internal/fs/path_prefix_test.go b/internal/fs/path_prefix_test.go new file mode 100644 index 000000000..dfdc69522 --- /dev/null +++ b/internal/fs/path_prefix_test.go @@ -0,0 +1,52 @@ +package fs + +import ( + "path/filepath" + "runtime" + "testing" +) + +func fromSlashAbs(p string) string { + if runtime.GOOS == "windows" { + if len(p) > 0 && p[0] == '/' { + p = "c:" + p + } + } + + return filepath.FromSlash(p) +} + +func TestHasPathPrefix(t *testing.T) { + var tests = []struct { + base, p string + result bool + }{ + {"", "", false}, + {"/", "", false}, + {"/", "x", false}, + {"x", "/", false}, + {"/", "/x", true}, + {"/x", "/y", false}, + {"/home/user/foo", "/home", false}, + {"/home/user/foo/", "/home", false}, + {"/home/user/foo", "/home/", false}, + {"/home/user/foo/", "/home/", false}, + {"/home/user/foo", "/home/user/foo/bar", true}, + {"/home/user/foo", "/home/user/foo/bar/baz/x/y/z", true}, + {"/home/user/foo", "/home/user/foobar", false}, + {"/home/user/Foo", "/home/user/foo/bar/baz", false}, + {"/home/user/foo", "/home/user/Foo/bar/baz", false}, + } + + for _, test := range tests { + t.Run("", func(t *testing.T) { + base := fromSlashAbs(test.base) + p := fromSlashAbs(test.p) + result := HasPathPrefix(base, p) + if result != test.result { + t.Fatalf("wrong result for HasPathPrefix(%q, %q): want %v, got %v", + base, p, test.result, result) + } + }) + } +}