From 2987f017b2e8395160cc7d20ea009622b9a6b3af Mon Sep 17 00:00:00 2001 From: Son Roy Almerol Date: Thu, 14 Nov 2024 15:25:12 -0500 Subject: [PATCH] check original file if open --- internal/agent/sftp/filter.go | 16 ++++++++++++++ internal/agent/sftp/windows.go | 33 ++++++++++++++++++++++++++++ internal/agent/snapshots/registry.go | 1 + internal/agent/snapshots/windows.go | 4 ++++ internal/backend/mount/mount.go | 5 ++--- 5 files changed, 56 insertions(+), 3 deletions(-) diff --git a/internal/agent/sftp/filter.go b/internal/agent/sftp/filter.go index 12ba5bc..196f1d5 100644 --- a/internal/agent/sftp/filter.go +++ b/internal/agent/sftp/filter.go @@ -3,8 +3,10 @@ package sftp import ( + "os" "regexp" "strings" + "time" "unicode" ) @@ -121,6 +123,16 @@ func compileExcludedPaths() []*regexp.Regexp { var excludedPathRegexes = compileExcludedPaths() func (h *SftpHandler) skipFile(path string) bool { + stat, err := os.Lstat(path) + if err != nil { + return true + } + + // skip probably opened files + if h.Snapshot.TimeStarted.Sub(stat.ModTime()) <= time.Minute { + return true + } + snapSplit := strings.Split(h.Snapshot.SnapshotPath, "\\") snapRoot := strings.Join(snapSplit[:len(snapSplit)-1], "\\") @@ -142,5 +154,9 @@ func (h *SftpHandler) skipFile(path string) bool { return true } + if isFileOpen(strings.Replace(pathWithoutSnap, "\\", ":\\", 1)) { + return true + } + return false } diff --git a/internal/agent/sftp/windows.go b/internal/agent/sftp/windows.go index 4a82512..2cebe2d 100644 --- a/internal/agent/sftp/windows.go +++ b/internal/agent/sftp/windows.go @@ -4,6 +4,7 @@ package sftp import ( "os" + "syscall" "unsafe" "golang.org/x/sys/windows" @@ -58,3 +59,35 @@ func invalidAttributes(path string) (bool, error) { return false, nil } + +const ERROR_SHARING_VIOLATION syscall.Errno = 32 + +func isFileOpen(path string) bool { + p, err := syscall.UTF16PtrFromString(path) + if err != nil { + return false + } + + // Use CreateFileW system call to open the file with read-write and exclusive access + // FILE_SHARE_NONE ensures that the file cannot be opened by any other process + h, err := syscall.CreateFile( + p, + syscall.GENERIC_READ|syscall.GENERIC_WRITE, + 0, + nil, + syscall.OPEN_EXISTING, + syscall.FILE_ATTRIBUTE_NORMAL, + 0, + ) + + if err != nil { + // ERROR_SHARING_VIOLATION means the file is already open by another process + if errno, ok := err.(syscall.Errno); ok && errno == ERROR_SHARING_VIOLATION { + return true + } + return false + } + + syscall.CloseHandle(h) + return false +} diff --git a/internal/agent/snapshots/registry.go b/internal/agent/snapshots/registry.go index f6663a6..40506df 100644 --- a/internal/agent/snapshots/registry.go +++ b/internal/agent/snapshots/registry.go @@ -16,6 +16,7 @@ type WinVSSSnapshot struct { SnapshotPath string `json:"path"` Id string `json:"vss_id"` LastAccessed time.Time `json:"last_accessed"` + TimeStarted time.Time `json:"time_started"` } type KnownSnapshots struct { diff --git a/internal/agent/snapshots/windows.go b/internal/agent/snapshots/windows.go index 3908b7b..f4f4cb5 100644 --- a/internal/agent/snapshots/windows.go +++ b/internal/agent/snapshots/windows.go @@ -46,6 +46,7 @@ func Snapshot(driveLetter string) (*WinVSSSnapshot, error) { _ = os.Remove(snapshotPath) } + timeStarted := time.Now() // Attempt to create a new snapshot err = vss.CreateLink(snapshotPath, volName) if err != nil { @@ -59,6 +60,8 @@ func Snapshot(driveLetter string) (*WinVSSSnapshot, error) { } else if strings.Contains(err.Error(), "already exists") { _ = vss.Remove(snapshotPath) _ = os.Remove(snapshotPath) + + timeStarted = time.Now() err = vss.CreateLink(snapshotPath, volName) if err != nil { return nil, fmt.Errorf("Snapshot: error creating snapshot (%s to %s) -> %w", volName, snapshotPath, err) @@ -80,6 +83,7 @@ func Snapshot(driveLetter string) (*WinVSSSnapshot, error) { SnapshotPath: snapshotPath, LastAccessed: time.Now(), Id: sc.ID, + TimeStarted: timeStarted, } knownSnaps.Save(newSnapshot) diff --git a/internal/backend/mount/mount.go b/internal/backend/mount/mount.go index abf6900..69bde06 100644 --- a/internal/backend/mount/mount.go +++ b/internal/backend/mount/mount.go @@ -52,10 +52,9 @@ func Mount(target *store.Target) (*AgentMount, error) { "--daemon", "--no-seek", "--read-only", - "--direct-io", "--uid", "0", "--gid", "0", - "--vfs-read-chunk-size", "0", + "--vfs-cache-mode", "minimal", "--sftp-disable-hashcheck", "--sftp-idle-timeout", "0", "--sftp-key-file", privKeyFile, @@ -75,7 +74,7 @@ func Mount(target *store.Target) (*AgentMount, error) { agentMount.Cmd = mnt - err = mnt.Start() + err = mnt.Run() if err != nil { agentMount.Unmount() return nil, fmt.Errorf("Mount: error starting rclone for sftp -> %w", err)