diff --git a/internal/agent/sftp/filelister.go b/internal/agent/sftp/filelister.go index 0834f7d..f986369 100644 --- a/internal/agent/sftp/filelister.go +++ b/internal/agent/sftp/filelister.go @@ -47,7 +47,7 @@ func (h *SftpHandler) FileLister(dirPath string) (*FileLister, error) { } fullPath := filepath.Join(dirPath, entry.Name()) - if skipFile(fullPath) { + if h.skipFile(fullPath) { continue } fileInfos = append(fileInfos, info) diff --git a/internal/agent/sftp/filter.go b/internal/agent/sftp/filter.go index 201a815..56ba269 100644 --- a/internal/agent/sftp/filter.go +++ b/internal/agent/sftp/filter.go @@ -1,3 +1,5 @@ +//go:build windows + package sftp import ( @@ -113,9 +115,16 @@ func compileExcludedPaths() []*regexp.Regexp { // Precompiled regex patterns for excluded paths var excludedPathRegexes = compileExcludedPaths() -func skipFile(path string) bool { - normalizedPath := strings.TrimPrefix(path, "C:\\Windows\\TEMP\\pbs-plus-vss\\") - normalizedPath = strings.ToUpper(normalizedPath) +func (h *SftpHandler) skipFile(path string) bool { + snapSplit := strings.Split(h.Snapshot.SnapshotPath, "\\") + snapRoot := strings.Join(snapSplit[:len(snapSplit)-1], "\\") + + pathWithoutSnap := strings.TrimPrefix(path, snapRoot) + normalizedPath := strings.ToUpper(strings.TrimPrefix(pathWithoutSnap, "\\")) + + if strings.TrimSpace(normalizedPath) == "" { + return false + } for _, regex := range excludedPathRegexes { if regex.MatchString(normalizedPath) { @@ -123,5 +132,15 @@ func skipFile(path string) bool { } } + isTmp, err := isTemporary(path) + if err != nil || isTmp { + return true + } + + probablyLocked, err := inconsistentSize(path) + if err != nil || probablyLocked { + return true + } + return false } diff --git a/internal/agent/sftp/windows.go b/internal/agent/sftp/windows.go new file mode 100644 index 0000000..011ea03 --- /dev/null +++ b/internal/agent/sftp/windows.go @@ -0,0 +1,69 @@ +//go:build windows + +package sftp + +import ( + "os" + "unsafe" + + "golang.org/x/sys/windows" +) + +// FileStandardInfo contains extended information for the file. +// FILE_STANDARD_INFO in WinBase.h +// https://docs.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-file_standard_info +type FileStandardInfo struct { + AllocationSize, EndOfFile int64 + NumberOfLinks uint32 + DeletePending, Directory bool +} + +type FileAttributeTagInfo struct { + FileAttributes uint32 + ReparseTag uint32 +} + +func isTemporary(path string) (bool, error) { + file, err := os.Open(path) + if err != nil { + return true, err + } + defer file.Close() + + at := &FileAttributeTagInfo{} + err = windows.GetFileInformationByHandleEx(windows.Handle(file.Fd()), windows.FileAttributeTagInfo, (*byte)(unsafe.Pointer(at)), uint32(unsafe.Sizeof(*at))) + if err != nil { + return true, err + } + + if at.FileAttributes&windows.FILE_ATTRIBUTE_TEMPORARY == windows.FILE_ATTRIBUTE_TEMPORARY { + return true, nil + } + + return false, nil +} + +func inconsistentSize(path string) (bool, error) { + file, err := os.Open(path) + if err != nil { + return true, err + } + defer file.Close() + + si := &FileStandardInfo{} + err = windows.GetFileInformationByHandleEx(windows.Handle(file.Fd()), windows.FileStandardInfo, (*byte)(unsafe.Pointer(si)), uint32(unsafe.Sizeof(*si))) + if err != nil { + return true, err + } + + stat, err := os.Lstat(path) + if err != nil { + return true, err + } + + if si.EndOfFile == stat.Size() { + return false, nil + } + + return true, nil +}