Skip to content

Commit

Permalink
fix: use copy when a rename spans volumes (#23785)
Browse files Browse the repository at this point in the history
When a file rename fails with EXDEV
(cross device or volume error), copy the
file and delete the original instead

Differs from master branch by overwriting
existing files instead of erring.

closes #22997
  • Loading branch information
davidby-influx authored Oct 12, 2022
1 parent 03ad344 commit 0913276
Showing 1 changed file with 62 additions and 2 deletions.
64 changes: 62 additions & 2 deletions pkg/file/file_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,13 @@
package file

import (
"errors"
"fmt"
"io"
"os"
"syscall"

errors2 "github.com/influxdata/influxdb/pkg/errors"
)

func SyncDir(dirName string) error {
Expand All @@ -29,7 +34,62 @@ func SyncDir(dirName string) error {
return dir.Close()
}

// RenameFile will rename the source to target using os function.
// RenameFileWithReplacement will replace any existing file at newpath with the contents
// of oldpath. It works also if it the rename spans over several file systems.
//
// If no file already exists at newpath, newpath will be created using the contents
// of oldpath. If this function returns successfully, the contents of newpath will
// be identical to oldpath, and oldpath will be removed.
func RenameFileWithReplacement(oldpath, newpath string) error {
if err := os.Rename(oldpath, newpath); !errors.Is(err, syscall.EXDEV) {
// note: also includes err == nil
return err
}

// move over filesystem boundaries, we have to copy.
// (if there was another error, it will likely fail a second time)
return MoveFileWithReplacement(oldpath, newpath)

}

// RenameFile renames oldpath to newpath, returning an error if newpath already
// exists. If this function returns successfully, the contents of newpath will
// be identical to oldpath, and oldpath will be removed.
func RenameFile(oldpath, newpath string) error {
return os.Rename(oldpath, newpath)
return RenameFileWithReplacement(oldpath, newpath)
}

func copyFile(src, dst string) (err error) {
in, err := os.Open(src)
if err != nil {
return err
}

out, err := os.Create(dst)
if err != nil {
return err
}

defer errors2.Capture(&err, out.Close)()

defer errors2.Capture(&err, in.Close)()

if _, err = io.Copy(out, in); err != nil {
return err
}

return out.Sync()
}

// MoveFileWithReplacement copies the file contents at `src` to `dst`.
// and deletes `src` on success.
//
// If the file at `dst` already exists, it will be truncated and its contents
// overwritten.
func MoveFileWithReplacement(src, dst string) error {
if err := copyFile(src, dst); err != nil {
return fmt.Errorf("copy: %w", err)
}

return os.Remove(src)
}

0 comments on commit 0913276

Please sign in to comment.