Skip to content

Commit

Permalink
fix: for Windows, copy snapshot files being backed up (#22551)
Browse files Browse the repository at this point in the history
On Windows, make copies of files for snapshots, because
Go does not support the FILE_SHARE_DELETE flag which
allows files (and links) to be deleted while open. This
causes temporary directories to be left behind after
backups.

closes #16289

(cherry picked from commit 3702fe8)
  • Loading branch information
davidby-influx committed Oct 14, 2021
1 parent a083fa9 commit bd94819
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 5 deletions.
17 changes: 17 additions & 0 deletions tsdb/engine/tsm1/copy_or_link_unix.go
@@ -0,0 +1,17 @@
//go:build !windows
// +build !windows

package tsm1

import (
"fmt"
"os"
)

// copyOrLink - allow substitution of a file copy for a hard link when running on Windows systems.
func copyOrLink(oldPath, newPath string) error {
if err := os.Link(oldPath, newPath); err != nil {
return fmt.Errorf("error creating hard link for backup from %s to %s: %q", oldPath, newPath, err)
}
return nil
}
46 changes: 46 additions & 0 deletions tsdb/engine/tsm1/copy_or_link_windows.go
@@ -0,0 +1,46 @@
//go:build windows
// +build windows

package tsm1

import (
"fmt"
"io"
"os"
)

// copyOrLink - Windows does not permit deleting a file with open file handles, so
// instead of hard links, make temporary copies of files that can then be deleted.
func copyOrLink(oldPath, newPath string) (returnErr error) {
rfd, err := os.Open(oldPath)
if err != nil {
return fmt.Errorf("error opening file for backup %s: %q", oldPath, err)
} else {
defer func() {
if e := rfd.Close(); returnErr == nil && e != nil {
returnErr = fmt.Errorf("error closing source file for backup %s: %q", oldPath, e)
}
}()
}
fi, err := rfd.Stat()
if err != nil {
fmt.Errorf("error collecting statistics from file for backup %s: %q", oldPath, err)
}
wfd, err := os.OpenFile(newPath, os.O_RDWR|os.O_CREATE, fi.Mode())
if err != nil {
return fmt.Errorf("error creating temporary file for backup %s: %q", newPath, err)
} else {
defer func() {
if e := wfd.Close(); returnErr == nil && e != nil {
returnErr = fmt.Errorf("error closing temporary file for backup %s: %q", newPath, e)
}
}()
}
if _, err := io.Copy(wfd, rfd); err != nil {
return fmt.Errorf("unable to copy file for backup from %s to %s: %q", oldPath, newPath, err)
}
if err := os.Chtimes(newPath, fi.ModTime(), fi.ModTime()); err != nil {
return fmt.Errorf("unable to set modification time on temporary backup file %s: %q", newPath, err)
}
return nil
}
16 changes: 16 additions & 0 deletions tsdb/engine/tsm1/engine_test.go
Expand Up @@ -480,6 +480,22 @@ func TestEngine_Backup(t *testing.T) {
if !strings.Contains(mostRecentFile, th.Name) || th.Name == "" {
t.Fatalf("file name doesn't match:\n\tgot: %s\n\texp: %s", th.Name, mostRecentFile)
}
storeDir := filepath.Dir(e.FileStore.Files()[0].Path())
dfd, err := os.Open(storeDir)
if err != nil {
t.Fatalf("cannot open filestore directory %s: %q", storeDir, err)
} else {
defer dfd.Close()
}
files, err := dfd.Readdirnames(0)
if err != nil {
t.Fatalf("cannot read directory %s: %q", storeDir, err)
}
for _, f := range files {
if strings.HasSuffix(f, tsm1.TmpTSMFileExtension) {
t.Fatalf("temporary directory for backup not cleaned up: %s", f)
}
}
}

func TestEngine_Export(t *testing.T) {
Expand Down
10 changes: 5 additions & 5 deletions tsdb/engine/tsm1/file_store.go
Expand Up @@ -1069,16 +1069,16 @@ func (f *FileStore) locations(key []byte, t int64, ascending bool) []*location {

// MakeSnapshotLinks creates hardlinks from the supplied TSMFiles to
// corresponding files under a supplied directory.
func (f *FileStore) MakeSnapshotLinks(destPath string, files []TSMFile) error {
func (f *FileStore) MakeSnapshotLinks(destPath string, files []TSMFile) (returnErr error) {
for _, tsmf := range files {
newpath := filepath.Join(destPath, filepath.Base(tsmf.Path()))
if err := os.Link(tsmf.Path(), newpath); err != nil {
return fmt.Errorf("error creating tsm hard link: %q", err)
if err := copyOrLink(tsmf.Path(), newpath); err != nil {
return err
}
if tf := tsmf.TombstoneStats(); tf.TombstoneExists {
newpath := filepath.Join(destPath, filepath.Base(tf.Path))
if err := os.Link(tf.Path, newpath); err != nil {
return fmt.Errorf("error creating tombstone hard link: %q", err)
if err := copyOrLink(tf.Path, newpath); err != nil {
return err
}
}
}
Expand Down

0 comments on commit bd94819

Please sign in to comment.