mirror of
https://github.com/restic/restic.git
synced 2025-08-25 23:47:29 +00:00
Merge pull request #5097 from MichaelEischer/fix-vss-metadata
backup: read extended metadata from snapshot
This commit is contained in:
@@ -10,6 +10,7 @@ import (
|
||||
|
||||
"github.com/restic/restic/internal/errors"
|
||||
"github.com/restic/restic/internal/options"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
)
|
||||
|
||||
// VSSConfig holds extended options of windows volume shadow copy service.
|
||||
@@ -127,17 +128,21 @@ func (fs *LocalVss) DeleteSnapshots() {
|
||||
|
||||
// OpenFile wraps the Open method of the underlying file system.
|
||||
func (fs *LocalVss) OpenFile(name string, flag int, perm os.FileMode) (File, error) {
|
||||
return os.OpenFile(fs.snapshotPath(name), flag, perm)
|
||||
return fs.FS.OpenFile(fs.snapshotPath(name), flag, perm)
|
||||
}
|
||||
|
||||
// Stat wraps the Stat method of the underlying file system.
|
||||
func (fs *LocalVss) Stat(name string) (os.FileInfo, error) {
|
||||
return os.Stat(fs.snapshotPath(name))
|
||||
return fs.FS.Stat(fs.snapshotPath(name))
|
||||
}
|
||||
|
||||
// Lstat wraps the Lstat method of the underlying file system.
|
||||
func (fs *LocalVss) Lstat(name string) (os.FileInfo, error) {
|
||||
return os.Lstat(fs.snapshotPath(name))
|
||||
return fs.FS.Lstat(fs.snapshotPath(name))
|
||||
}
|
||||
|
||||
func (fs *LocalVss) NodeFromFileInfo(path string, fi os.FileInfo, ignoreXattrListError bool) (*restic.Node, error) {
|
||||
return fs.FS.NodeFromFileInfo(fs.snapshotPath(path), fi, ignoreXattrListError)
|
||||
}
|
||||
|
||||
// isMountPointIncluded is true if given mountpoint included by user.
|
||||
|
@@ -5,13 +5,18 @@ package fs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
ole "github.com/go-ole/go-ole"
|
||||
"github.com/restic/restic/internal/options"
|
||||
rtest "github.com/restic/restic/internal/test"
|
||||
)
|
||||
|
||||
func matchStrings(ptrs []string, strs []string) bool {
|
||||
@@ -284,3 +289,56 @@ func TestParseProvider(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestVSSFS(t *testing.T) {
|
||||
if runtime.GOOS != "windows" || HasSufficientPrivilegesForVSS() != nil {
|
||||
t.Skip("vss fs test can only be run on windows with admin privileges")
|
||||
}
|
||||
|
||||
cfg, err := ParseVSSConfig(options.Options{})
|
||||
rtest.OK(t, err)
|
||||
|
||||
errorHandler := func(item string, err error) {
|
||||
t.Fatalf("unexpected error (%v)", err)
|
||||
}
|
||||
messageHandler := func(msg string, args ...interface{}) {
|
||||
if strings.HasPrefix(msg, "creating VSS snapshot for") || strings.HasPrefix(msg, "successfully created snapshot") {
|
||||
return
|
||||
}
|
||||
t.Fatalf("unexpected message (%s)", fmt.Sprintf(msg, args))
|
||||
}
|
||||
|
||||
localVss := NewLocalVss(errorHandler, messageHandler, cfg)
|
||||
defer localVss.DeleteSnapshots()
|
||||
|
||||
tempdir := t.TempDir()
|
||||
tempfile := filepath.Join(tempdir, "file")
|
||||
rtest.OK(t, os.WriteFile(tempfile, []byte("example"), 0o600))
|
||||
|
||||
// trigger snapshot creation and
|
||||
// capture FI while file still exists (should already be within the snapshot)
|
||||
origFi, err := localVss.Stat(tempfile)
|
||||
rtest.OK(t, err)
|
||||
|
||||
// remove original file
|
||||
rtest.OK(t, os.Remove(tempfile))
|
||||
|
||||
statFi, err := localVss.Stat(tempfile)
|
||||
rtest.OK(t, err)
|
||||
rtest.Equals(t, origFi.Mode(), statFi.Mode())
|
||||
|
||||
lstatFi, err := localVss.Lstat(tempfile)
|
||||
rtest.OK(t, err)
|
||||
rtest.Equals(t, origFi.Mode(), lstatFi.Mode())
|
||||
|
||||
f, err := localVss.OpenFile(tempfile, os.O_RDONLY, 0)
|
||||
rtest.OK(t, err)
|
||||
data, err := io.ReadAll(f)
|
||||
rtest.OK(t, err)
|
||||
rtest.Equals(t, "example", string(data), "unexpected file content")
|
||||
rtest.OK(t, f.Close())
|
||||
|
||||
node, err := localVss.NodeFromFileInfo(tempfile, statFi, false)
|
||||
rtest.OK(t, err)
|
||||
rtest.Equals(t, node.Mode, statFi.Mode())
|
||||
}
|
||||
|
@@ -232,7 +232,7 @@ func (r *readerFile) Close() error {
|
||||
var _ File = &readerFile{}
|
||||
|
||||
// fakeFile implements all File methods, but only returns errors for anything
|
||||
// except Stat() and Name().
|
||||
// except Stat()
|
||||
type fakeFile struct {
|
||||
name string
|
||||
os.FileInfo
|
||||
@@ -257,10 +257,6 @@ func (f fakeFile) Stat() (os.FileInfo, error) {
|
||||
return f.FileInfo, nil
|
||||
}
|
||||
|
||||
func (f fakeFile) Name() string {
|
||||
return f.name
|
||||
}
|
||||
|
||||
// fakeDir implements Readdirnames and Readdir, everything else is delegated to fakeFile.
|
||||
type fakeDir struct {
|
||||
entries []os.FileInfo
|
||||
|
@@ -34,5 +34,4 @@ type File interface {
|
||||
|
||||
Readdirnames(n int) ([]string, error)
|
||||
Stat() (os.FileInfo, error)
|
||||
Name() string
|
||||
}
|
||||
|
Reference in New Issue
Block a user