Last active
October 16, 2019 17:29
-
-
Save caelifer/ea938e61aa8d03abde226076eb53806a to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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