Add safe rename functions at fs level
parent
0858b2570d
commit
9fcf27a25b
|
@ -0,0 +1,130 @@
|
||||||
|
package fs_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/influxdata/influxdb/pkg/fs"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRenameFileWithReplacement(t *testing.T) {
|
||||||
|
t.Run("exists", func(t *testing.T) {
|
||||||
|
oldpath := MustCreateTempFile()
|
||||||
|
newpath := MustCreateTempFile()
|
||||||
|
defer MustRemoveAll(oldpath)
|
||||||
|
defer MustRemoveAll(newpath)
|
||||||
|
|
||||||
|
oldContents := MustReadAllFile(oldpath)
|
||||||
|
newContents := MustReadAllFile(newpath)
|
||||||
|
|
||||||
|
if got, exp := oldContents, oldpath; got != exp {
|
||||||
|
t.Fatalf("got contents %q, expected %q", got, exp)
|
||||||
|
} else if got, exp := newContents, newpath; got != exp {
|
||||||
|
t.Fatalf("got contents %q, expected %q", got, exp)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := fs.RenameFileWithReplacement(oldpath, newpath); err != nil {
|
||||||
|
t.Fatalf("ReplaceFileIfExists returned an error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := fs.SyncDir(filepath.Dir(oldpath)); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contents of newpath will now be equivalent to oldpath' contents.
|
||||||
|
newContents = MustReadAllFile(newpath)
|
||||||
|
if newContents != oldContents {
|
||||||
|
t.Fatalf("contents for files differ: %q versus %q", newContents, oldContents)
|
||||||
|
}
|
||||||
|
|
||||||
|
// oldpath will be removed.
|
||||||
|
if MustFileExists(oldpath) {
|
||||||
|
t.Fatalf("file %q still exists, but it shouldn't", oldpath)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("not exists", func(t *testing.T) {
|
||||||
|
oldpath := MustCreateTempFile()
|
||||||
|
defer MustRemoveAll(oldpath)
|
||||||
|
|
||||||
|
oldContents := MustReadAllFile(oldpath)
|
||||||
|
if got, exp := oldContents, oldpath; got != exp {
|
||||||
|
t.Fatalf("got contents %q, expected %q", got, exp)
|
||||||
|
}
|
||||||
|
|
||||||
|
root := filepath.Dir(oldpath)
|
||||||
|
newpath := filepath.Join(root, "foo")
|
||||||
|
if err := fs.RenameFileWithReplacement(oldpath, newpath); err != nil {
|
||||||
|
t.Fatalf("ReplaceFileIfExists returned an error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := fs.SyncDir(filepath.Dir(oldpath)); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contents of newpath will now be equivalent to oldpath's contents.
|
||||||
|
newContents := MustReadAllFile(newpath)
|
||||||
|
if newContents != oldContents {
|
||||||
|
t.Fatalf("contents for files differ: %q versus %q", newContents, oldContents)
|
||||||
|
}
|
||||||
|
|
||||||
|
// oldpath will be removed.
|
||||||
|
if MustFileExists(oldpath) {
|
||||||
|
t.Fatalf("file %q still exists, but it shouldn't", oldpath)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustCreateTempFile creates a temporary file returning the path to the file.
|
||||||
|
//
|
||||||
|
// MustCreateTempFile writes the absolute path to the file into the file itself.
|
||||||
|
// It panics if there is an error.
|
||||||
|
func MustCreateTempFile() string {
|
||||||
|
f, err := ioutil.TempFile("", "fs-test")
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("failed to create temp file: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
name := f.Name()
|
||||||
|
f.WriteString(name)
|
||||||
|
if err := f.Close(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
func MustRemoveAll(path string) {
|
||||||
|
if err := os.RemoveAll(path); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustFileExists determines if a file exists, panicking if any error
|
||||||
|
// (other than one associated with the file not existing) is returned.
|
||||||
|
func MustFileExists(path string) bool {
|
||||||
|
_, err := os.Stat(path)
|
||||||
|
if err == nil {
|
||||||
|
return true
|
||||||
|
} else if os.IsNotExist(err) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustReadAllFile reads the contents of path, panicking if there is an error.
|
||||||
|
func MustReadAllFile(path string) string {
|
||||||
|
fd, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer fd.Close()
|
||||||
|
|
||||||
|
data, err := ioutil.ReadAll(fd)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return string(data)
|
||||||
|
}
|
|
@ -3,10 +3,25 @@
|
||||||
package fs
|
package fs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"syscall"
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// A FileExistsError is returned when an operation cannot be completed due to a
|
||||||
|
// file already existing.
|
||||||
|
type FileExistsError struct {
|
||||||
|
path string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFileExistsError(path string) FileExistsError {
|
||||||
|
return FileExistsError{path: path}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e FileExistsError) Error() string {
|
||||||
|
return fmt.Sprintf("operation not allowed, file %q exists", e.path)
|
||||||
|
}
|
||||||
|
|
||||||
// SyncDir flushes any file renames to the filesystem.
|
// SyncDir flushes any file renames to the filesystem.
|
||||||
func SyncDir(dirName string) error {
|
func SyncDir(dirName string) error {
|
||||||
// fsync the dir to flush the rename
|
// fsync the dir to flush the rename
|
||||||
|
@ -39,3 +54,14 @@ func SyncDir(dirName string) error {
|
||||||
func RenameFileWithReplacement(oldpath, newpath string) error {
|
func RenameFileWithReplacement(oldpath, newpath string) error {
|
||||||
return os.Rename(oldpath, newpath)
|
return os.Rename(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 {
|
||||||
|
if _, err := os.Stat(newpath); err == nil {
|
||||||
|
return newFileExistsError(newpath)
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.Rename(oldpath, newpath)
|
||||||
|
}
|
||||||
|
|
|
@ -21,3 +21,16 @@ func RenameFileWithReplacement(oldpath, newpath string) error {
|
||||||
|
|
||||||
return os.Rename(oldpath, newpath)
|
return os.Rename(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 {
|
||||||
|
if _, err := os.Stat(newpath); err == nil {
|
||||||
|
// os.Rename on Windows will return an error if the file exists, but it's
|
||||||
|
// preferable to keep the errors the same across platforms.
|
||||||
|
return newFileExistsError(newpath)
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.Rename(oldpath, newpath)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue