mirror of
https://github.com/octoleo/restic.git
synced 2024-12-22 10:58:55 +00:00
Merge pull request #2274 from fgma/master
Support for Volume Shadow Copy Service (VSS) on windows
This commit is contained in:
commit
31b8d7a639
12
changelog/unreleased/issue-340
Normal file
12
changelog/unreleased/issue-340
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
Enhancement: Add support for Volume Shadow Copy Service (VSS) on Windows
|
||||||
|
|
||||||
|
Volume Shadow Copy Service allows read access to files that are locked by
|
||||||
|
another process using an exclusive lock through a filesystem snapshot. Restic
|
||||||
|
was unable to backup those files before. This update enables backing up these
|
||||||
|
files.
|
||||||
|
|
||||||
|
This needs to be enabled explicitely using the --use-fs-snapshot option of the
|
||||||
|
backup command.
|
||||||
|
|
||||||
|
https://github.com/restic/restic/issues/340
|
||||||
|
https://github.com/restic/restic/pull/2274
|
@ -10,6 +10,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -96,6 +97,7 @@ type BackupOptions struct {
|
|||||||
TimeStamp string
|
TimeStamp string
|
||||||
WithAtime bool
|
WithAtime bool
|
||||||
IgnoreInode bool
|
IgnoreInode bool
|
||||||
|
UseFsSnapshot bool
|
||||||
}
|
}
|
||||||
|
|
||||||
var backupOptions BackupOptions
|
var backupOptions BackupOptions
|
||||||
@ -129,6 +131,9 @@ func init() {
|
|||||||
f.StringVar(&backupOptions.TimeStamp, "time", "", "`time` of the backup (ex. '2012-11-01 22:08:41') (default: now)")
|
f.StringVar(&backupOptions.TimeStamp, "time", "", "`time` of the backup (ex. '2012-11-01 22:08:41') (default: now)")
|
||||||
f.BoolVar(&backupOptions.WithAtime, "with-atime", false, "store the atime for all files and directories")
|
f.BoolVar(&backupOptions.WithAtime, "with-atime", false, "store the atime for all files and directories")
|
||||||
f.BoolVar(&backupOptions.IgnoreInode, "ignore-inode", false, "ignore inode number changes when checking for modified files")
|
f.BoolVar(&backupOptions.IgnoreInode, "ignore-inode", false, "ignore inode number changes when checking for modified files")
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
f.BoolVar(&backupOptions.UseFsSnapshot, "use-fs-snapshot", false, "use filesystem snapshot where possible (currently only Windows VSS)")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// filterExisting returns a slice of all existing items, or an error if no
|
// filterExisting returns a slice of all existing items, or an error if no
|
||||||
@ -550,6 +555,25 @@ func runBackup(opts BackupOptions, gopts GlobalOptions, term *termstatus.Termina
|
|||||||
}
|
}
|
||||||
|
|
||||||
var targetFS fs.FS = fs.Local{}
|
var targetFS fs.FS = fs.Local{}
|
||||||
|
if runtime.GOOS == "windows" && opts.UseFsSnapshot {
|
||||||
|
if !fs.HasSufficientPrivilegesForVSS() {
|
||||||
|
return errors.Fatal("user doesn't have sufficient privileges to use VSS snapshots\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
errorHandler := func(item string, err error) error {
|
||||||
|
return p.Error(item, nil, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
messageHandler := func(msg string, args ...interface{}) {
|
||||||
|
if !gopts.JSON {
|
||||||
|
p.P(msg, args...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
localVss := fs.NewLocalVss(errorHandler, messageHandler)
|
||||||
|
defer localVss.DeleteSnapshots()
|
||||||
|
targetFS = localVss
|
||||||
|
}
|
||||||
if opts.Stdin {
|
if opts.Stdin {
|
||||||
if !gopts.JSON {
|
if !gopts.JSON {
|
||||||
p.V("read data from stdin")
|
p.V("read data from stdin")
|
||||||
|
@ -21,6 +21,7 @@ import (
|
|||||||
|
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
"github.com/restic/restic/internal/filter"
|
"github.com/restic/restic/internal/filter"
|
||||||
|
"github.com/restic/restic/internal/fs"
|
||||||
"github.com/restic/restic/internal/repository"
|
"github.com/restic/restic/internal/repository"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
rtest "github.com/restic/restic/internal/test"
|
rtest "github.com/restic/restic/internal/test"
|
||||||
@ -281,11 +282,21 @@ func testSetupBackupData(t testing.TB, env *testEnvironment) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestBackup(t *testing.T) {
|
func TestBackup(t *testing.T) {
|
||||||
|
testBackup(t, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBackupWithFilesystemSnapshots(t *testing.T) {
|
||||||
|
if runtime.GOOS == "windows" && fs.HasSufficientPrivilegesForVSS() {
|
||||||
|
testBackup(t, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testBackup(t *testing.T, useFsSnapshot bool) {
|
||||||
env, cleanup := withTestEnvironment(t)
|
env, cleanup := withTestEnvironment(t)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
testSetupBackupData(t, env)
|
testSetupBackupData(t, env)
|
||||||
opts := BackupOptions{}
|
opts := BackupOptions{UseFsSnapshot: useFsSnapshot}
|
||||||
|
|
||||||
// first backup
|
// first backup
|
||||||
testRunBackup(t, filepath.Dir(env.testdata), []string{"testdata"}, opts, env.gopts)
|
testRunBackup(t, filepath.Dir(env.testdata), []string{"testdata"}, opts, env.gopts)
|
||||||
|
@ -50,7 +50,24 @@ still get a nice live status display. Be aware that the live status shows the
|
|||||||
processed files and not the transferred data. Transferred volume might be lower
|
processed files and not the transferred data. Transferred volume might be lower
|
||||||
(due to de-duplication) or higher.
|
(due to de-duplication) or higher.
|
||||||
|
|
||||||
If you run the command again, restic will create another snapshot of
|
On Windows, the ``--use-fs-snapshot`` option will use Windows' Volume Shadow Copy
|
||||||
|
Service (VSS) when creating backups. Restic will transparently create a VSS
|
||||||
|
snapshot for each volume that contains files to backup. Files are read from the
|
||||||
|
VSS snapshot instead of the regular filesystem. This allows to backup files that are
|
||||||
|
exclusively locked by another process during the backup.
|
||||||
|
|
||||||
|
By default VSS ignores Outlook OST files. This is not a restriction of restic
|
||||||
|
but the default Windows VSS configuration. The files not to snapshot are
|
||||||
|
configured in the Windows registry under the following key:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\BackupRestore\FilesNotToSnapshot
|
||||||
|
|
||||||
|
For more details refer the official Windows documentation e.g. the article
|
||||||
|
``Registry Keys and Values for Backup and Restore``.
|
||||||
|
|
||||||
|
If you run the backup command again, restic will create another snapshot of
|
||||||
your data, but this time it's even faster and no new data was added to the
|
your data, but this time it's even faster and no new data was added to the
|
||||||
repository (since all data is already there). This is de-duplication at work!
|
repository (since all data is already there). This is de-duplication at work!
|
||||||
|
|
||||||
|
@ -107,6 +107,7 @@ command:
|
|||||||
--stdin-filename filename filename to use when reading from stdin (default "stdin")
|
--stdin-filename filename filename to use when reading from stdin (default "stdin")
|
||||||
--tag tag add a tag for the new snapshot (can be specified multiple times)
|
--tag tag add a tag for the new snapshot (can be specified multiple times)
|
||||||
--time time time of the backup (ex. '2012-11-01 22:08:41') (default: now)
|
--time time time of the backup (ex. '2012-11-01 22:08:41') (default: now)
|
||||||
|
--use-fs-snapshot use filesystem snapshot where possible (currently only Windows VSS)
|
||||||
--with-atime store the atime for all files and directories
|
--with-atime store the atime for all files and directories
|
||||||
|
|
||||||
Global Flags:
|
Global Flags:
|
||||||
|
1
go.mod
1
go.mod
@ -11,6 +11,7 @@ require (
|
|||||||
github.com/dchest/siphash v1.2.2
|
github.com/dchest/siphash v1.2.2
|
||||||
github.com/dnaeon/go-vcr v1.0.1 // indirect
|
github.com/dnaeon/go-vcr v1.0.1 // indirect
|
||||||
github.com/elithrar/simple-scrypt v1.3.0
|
github.com/elithrar/simple-scrypt v1.3.0
|
||||||
|
github.com/go-ole/go-ole v1.2.4
|
||||||
github.com/google/go-cmp v0.5.2
|
github.com/google/go-cmp v0.5.2
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 // indirect
|
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 // indirect
|
||||||
github.com/hashicorp/golang-lru v0.5.4
|
github.com/hashicorp/golang-lru v0.5.4
|
||||||
|
2
go.sum
2
go.sum
@ -95,6 +95,8 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
|
|||||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
|
github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
|
||||||
|
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
|
188
internal/fs/fs_local_vss.go
Normal file
188
internal/fs/fs_local_vss.go
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
package fs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/restic/restic/internal/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrorHandler is used to report errors via callback
|
||||||
|
type ErrorHandler func(item string, err error) error
|
||||||
|
|
||||||
|
// MessageHandler is used to report errors/messages via callbacks.
|
||||||
|
type MessageHandler func(msg string, args ...interface{})
|
||||||
|
|
||||||
|
// LocalVss is a wrapper around the local file system which uses windows volume
|
||||||
|
// shadow copy service (VSS) in a transparent way.
|
||||||
|
type LocalVss struct {
|
||||||
|
FS
|
||||||
|
snapshots map[string]VssSnapshot
|
||||||
|
failedSnapshots map[string]struct{}
|
||||||
|
mutex *sync.RWMutex
|
||||||
|
msgError ErrorHandler
|
||||||
|
msgMessage MessageHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
// statically ensure that LocalVss implements FS.
|
||||||
|
var _ FS = &LocalVss{}
|
||||||
|
|
||||||
|
// NewLocalVss creates a new wrapper around the windows filesystem using volume
|
||||||
|
// shadow copy service to access locked files.
|
||||||
|
func NewLocalVss(msgError ErrorHandler, msgMessage MessageHandler) *LocalVss {
|
||||||
|
return &LocalVss{
|
||||||
|
FS: Local{},
|
||||||
|
snapshots: make(map[string]VssSnapshot),
|
||||||
|
failedSnapshots: make(map[string]struct{}),
|
||||||
|
mutex: &sync.RWMutex{},
|
||||||
|
msgError: msgError,
|
||||||
|
msgMessage: msgMessage,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteSnapshots deletes all snapshots that were created automatically.
|
||||||
|
func (fs *LocalVss) DeleteSnapshots() {
|
||||||
|
fs.mutex.Lock()
|
||||||
|
defer fs.mutex.Unlock()
|
||||||
|
|
||||||
|
activeSnapshots := make(map[string]VssSnapshot)
|
||||||
|
|
||||||
|
for volumeName, snapshot := range fs.snapshots {
|
||||||
|
if err := snapshot.Delete(); err != nil {
|
||||||
|
fs.msgError(volumeName, errors.Errorf("failed to delete VSS snapshot: %s", err))
|
||||||
|
activeSnapshots[volumeName] = snapshot
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.snapshots = activeSnapshots
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open wraps the Open method of the underlying file system.
|
||||||
|
func (fs *LocalVss) Open(name string) (File, error) {
|
||||||
|
return os.Open(fs.snapshotPath(name))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stat wraps the Open method of the underlying file system.
|
||||||
|
func (fs *LocalVss) Stat(name string) (os.FileInfo, error) {
|
||||||
|
return os.Stat(fs.snapshotPath(name))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lstat wraps the Open method of the underlying file system.
|
||||||
|
func (fs *LocalVss) Lstat(name string) (os.FileInfo, error) {
|
||||||
|
return os.Lstat(fs.snapshotPath(name))
|
||||||
|
}
|
||||||
|
|
||||||
|
// snapshotPath returns the path inside a VSS snapshots if it already exists.
|
||||||
|
// If the path is not yet available as a snapshot, a snapshot is created.
|
||||||
|
// If creation of a snapshot fails the file's original path is returned as
|
||||||
|
// a fallback.
|
||||||
|
func (fs *LocalVss) snapshotPath(path string) string {
|
||||||
|
|
||||||
|
fixPath := fixpath(path)
|
||||||
|
|
||||||
|
if strings.HasPrefix(fixPath, `\\?\UNC\`) {
|
||||||
|
// UNC network shares are currently not supported so we access the regular file
|
||||||
|
// without snapshotting
|
||||||
|
// TODO: right now there is a problem in fixpath(): "\\host\share" is not returned as a UNC path
|
||||||
|
// "\\host\share\" is returned as a valid UNC path
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
fixPath = strings.TrimPrefix(fixpath(path), `\\?\`)
|
||||||
|
fixPathLower := strings.ToLower(fixPath)
|
||||||
|
volumeName := filepath.VolumeName(fixPath)
|
||||||
|
volumeNameLower := strings.ToLower(volumeName)
|
||||||
|
|
||||||
|
fs.mutex.RLock()
|
||||||
|
|
||||||
|
// ensure snapshot for volume exists
|
||||||
|
_, snapshotExists := fs.snapshots[volumeNameLower]
|
||||||
|
_, snapshotFailed := fs.failedSnapshots[volumeNameLower]
|
||||||
|
if !snapshotExists && !snapshotFailed {
|
||||||
|
fs.mutex.RUnlock()
|
||||||
|
fs.mutex.Lock()
|
||||||
|
defer fs.mutex.Unlock()
|
||||||
|
|
||||||
|
_, snapshotExists = fs.snapshots[volumeNameLower]
|
||||||
|
_, snapshotFailed = fs.failedSnapshots[volumeNameLower]
|
||||||
|
|
||||||
|
if !snapshotExists && !snapshotFailed {
|
||||||
|
vssVolume := volumeNameLower + string(filepath.Separator)
|
||||||
|
fs.msgMessage("creating VSS snapshot for [%s]\n", vssVolume)
|
||||||
|
|
||||||
|
if snapshot, err := NewVssSnapshot(vssVolume, 120, fs.msgError); err != nil {
|
||||||
|
fs.msgError(vssVolume, errors.Errorf("failed to create snapshot for [%s]: %s\n",
|
||||||
|
vssVolume, err))
|
||||||
|
fs.failedSnapshots[volumeNameLower] = struct{}{}
|
||||||
|
} else {
|
||||||
|
fs.snapshots[volumeNameLower] = snapshot
|
||||||
|
fs.msgMessage("successfully created snapshot for [%s]\n", vssVolume)
|
||||||
|
if len(snapshot.mountPointInfo) > 0 {
|
||||||
|
fs.msgMessage("mountpoints in snapshot volume [%s]:\n", vssVolume)
|
||||||
|
for mp, mpInfo := range snapshot.mountPointInfo {
|
||||||
|
info := ""
|
||||||
|
if !mpInfo.IsSnapshotted() {
|
||||||
|
info = " (not snapshotted)"
|
||||||
|
}
|
||||||
|
fs.msgMessage(" - %s%s\n", mp, info)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
defer fs.mutex.RUnlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
var snapshotPath string
|
||||||
|
if snapshot, ok := fs.snapshots[volumeNameLower]; ok {
|
||||||
|
// handle case when data is inside mountpoint
|
||||||
|
for mountPoint, info := range snapshot.mountPointInfo {
|
||||||
|
if HasPathPrefix(mountPoint, fixPathLower) {
|
||||||
|
if !info.IsSnapshotted() {
|
||||||
|
// requested path is under mount point but mount point is
|
||||||
|
// not available as a snapshot (e.g. no filesystem support,
|
||||||
|
// removable media, etc.)
|
||||||
|
// -> try to backup without a snapshot
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
// filepath.rel() should always succeed because we checked that fixPath is either
|
||||||
|
// the same path or below mountPoint and operation is case-insensitive
|
||||||
|
relativeToMount, err := filepath.Rel(mountPoint, fixPath)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
snapshotPath = fs.Join(info.GetSnapshotDeviceObject(), relativeToMount)
|
||||||
|
|
||||||
|
if snapshotPath == info.GetSnapshotDeviceObject() {
|
||||||
|
snapshotPath += string(filepath.Separator)
|
||||||
|
}
|
||||||
|
|
||||||
|
return snapshotPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// requested data is directly on the volume, not inside a mount point
|
||||||
|
snapshotPath = fs.Join(snapshot.GetSnapshotDeviceObject(),
|
||||||
|
strings.TrimPrefix(fixPath, volumeName))
|
||||||
|
if snapshotPath == snapshot.GetSnapshotDeviceObject() {
|
||||||
|
snapshotPath = snapshotPath + string(filepath.Separator)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// no snapshot is available for the requested path:
|
||||||
|
// -> try to backup without a snapshot
|
||||||
|
// TODO: log warning?
|
||||||
|
snapshotPath = path
|
||||||
|
}
|
||||||
|
|
||||||
|
return snapshotPath
|
||||||
|
}
|
49
internal/fs/vss.go
Normal file
49
internal/fs/vss.go
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package fs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/restic/restic/internal/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MountPoint is a dummy for non-windows platforms to let client code compile.
|
||||||
|
type MountPoint struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSnapshotted is true if this mount point was snapshotted successfully.
|
||||||
|
func (p *MountPoint) IsSnapshotted() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSnapshotDeviceObject returns root path to access the snapshot files and folders.
|
||||||
|
func (p *MountPoint) GetSnapshotDeviceObject() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// VssSnapshot is a dummy for non-windows platforms to let client code compile.
|
||||||
|
type VssSnapshot struct {
|
||||||
|
mountPointInfo map[string]MountPoint
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasSufficientPrivilegesForVSS returns true if the user is allowed to use VSS.
|
||||||
|
func HasSufficientPrivilegesForVSS() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewVssSnapshot creates a new vss snapshot. If creating the snapshots doesn't
|
||||||
|
// finish within the timeout an error is returned.
|
||||||
|
func NewVssSnapshot(
|
||||||
|
volume string, timeoutInSeconds uint, msgError ErrorHandler) (VssSnapshot, error) {
|
||||||
|
return VssSnapshot{}, errors.New("VSS snapshots are only supported on windows")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete deletes the created snapshot.
|
||||||
|
func (p *VssSnapshot) Delete() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSnapshotDeviceObject returns root path to access the snapshot files
|
||||||
|
// and folders.
|
||||||
|
func (p *VssSnapshot) GetSnapshotDeviceObject() string {
|
||||||
|
return ""
|
||||||
|
}
|
1066
internal/fs/vss_windows.go
Normal file
1066
internal/fs/vss_windows.go
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user