Skip to content

Instantly share code, notes, and snippets.

@caelifer caelifer/moveFile.go
Last active Oct 16, 2019

Embed
What would you like to do?
import (
"io"
"io/ioutil"
"os"
"path/filepath"
)
// moveFile safely moves files across different file systems.
func moveFile(src, dest string) error {
// Fast path: use os.Rename().
if err := os.Rename(src, dest); err != nil {
// Check the reason of failure.
if _, ok := err.(*os.LinkError); ok {
// Fall back to safe rename across filesystems.
return safeMove(src, dest)
}
// Unrecoverable error; toss it back to caller.
return err
}
return nil
}
// safeMove moves files across system volumes and different filesystems. It
// utilizes copy/move/delete strategy to avoid possible race conditions.
func safeMove(src, dest string) error {
// Open source file for reading.
srcFile, err := os.Open(src)
if err != nil {
return err
}
defer srcFile.Close()
// Get source file permissions.
info, err := srcFile.Stat()
if err != nil {
return err
}
// Create a temporary file with unique name in the destination's directory.
tmpFile, err := ioutil.TempFile(filepath.Dir(dest), filepath.Base(dest))
if err != nil {
return err
}
tmpPath := tmpFile.Name()
// Always remove temporary file.
defer os.Remove(tmpPath)
// Copy bytes.
if _, err = io.Copy(tmpFile, srcFile); err != nil {
defer tmpFile.Close() // prevents file descriptor leak.
return err
}
// Close tmpFile to flush all writes.
if err = tmpFile.Close(); err != nil {
return err
}
// Atomically rename. This is safe now since both files are in the same directory.
if err = os.Rename(tmpPath, dest); err != nil {
return err
}
// Change permission bits on destination to match the source file.
if err = os.Chmod(dest, info.Mode().Perm()); err != nil {
return err
}
// Finally, if all is good, delete source file.
return os.Remove(src)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.