From 01be9bee62dd8f02b5551a43e5f90acac8f5bdf5 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Wed, 15 Apr 2020 18:05:49 +0300 Subject: [PATCH] Fix moving files between different drives and filesystems --- pkg/util/pathutil/util.go | 4 +- pkg/util/rename/rename.go | 73 +++++++++++++++++++++++++++++++++++++ pkg/util/updater/updater.go | 11 +++--- 3 files changed, 82 insertions(+), 6 deletions(-) create mode 100644 pkg/util/rename/rename.go diff --git a/pkg/util/pathutil/util.go b/pkg/util/pathutil/util.go index 56b0487db5..d3dc14613b 100644 --- a/pkg/util/pathutil/util.go +++ b/pkg/util/pathutil/util.go @@ -5,6 +5,8 @@ import ( "io/ioutil" "os" "path/filepath" + + "github.com/SkycoinProject/skywire-mainnet/pkg/util/rename" ) const ( @@ -43,7 +45,7 @@ func AtomicWriteFile(filename string, data []byte) error { return err } - if err := os.Rename(tempFilePath, filename); err != nil { + if err := rename.Rename(tempFilePath, filename); err != nil { return err } diff --git a/pkg/util/rename/rename.go b/pkg/util/rename/rename.go new file mode 100644 index 0000000000..75d6b8e600 --- /dev/null +++ b/pkg/util/rename/rename.go @@ -0,0 +1,73 @@ +package rename + +import ( + "fmt" + "io" + "log" + "os" +) + +const crossDeviceError = "invalid cross-device link" + +// Rename renames (moves) oldPath to newPath using os.Rename. +// If paths are located on different drives or filesystems, os.Rename fails. +// In that case, Rename uses a workaround by copying oldPath to newPath and removing oldPath thereafter. +func Rename(oldPath, newPath string) error { + if err := os.Rename(oldPath, newPath); err == nil || err.Error() != crossDeviceError { + return err + } + + stat, err := os.Stat(oldPath) + if err != nil { + return fmt.Errorf("stat: %w", err) + } + + if !stat.Mode().IsRegular() { + return fmt.Errorf("is regular: %w", err) + } + + // Paths are located on different devices. + if err := move(oldPath, newPath); err != nil { + return fmt.Errorf("move: %w", err) + } + + if err := os.Chmod(newPath, stat.Mode()); err != nil { + return fmt.Errorf("chmod: %w", err) + } + + if err := os.Remove(oldPath); err != nil { + return fmt.Errorf("remove: %w", err) + } + + return nil +} + +func move(oldPath string, newPath string) error { + inputFile, err := os.Open(oldPath) // nolint:gosec + if err != nil { + return fmt.Errorf("open: %w", err) + } + + defer func() { + if err := inputFile.Close(); err != nil { + log.Printf("Failed to close file %q: %v", inputFile.Name(), err) + } + }() + + outputFile, err := os.Create(newPath) + if err != nil { + return fmt.Errorf("create: %w", err) + } + + defer func() { + if err := outputFile.Close(); err != nil { + log.Printf("Failed to close file %q: %v", outputFile.Name(), err) + } + }() + + if _, err = io.Copy(outputFile, inputFile); err != nil { + return fmt.Errorf("copy: %w", err) + } + + return nil +} diff --git a/pkg/util/updater/updater.go b/pkg/util/updater/updater.go index 9e76a742b3..275d54500f 100644 --- a/pkg/util/updater/updater.go +++ b/pkg/util/updater/updater.go @@ -24,6 +24,7 @@ import ( "github.com/SkycoinProject/skywire-mainnet/pkg/restart" "github.com/SkycoinProject/skywire-mainnet/pkg/util/buildinfo" + "github.com/SkycoinProject/skywire-mainnet/pkg/util/rename" ) const ( @@ -180,13 +181,13 @@ func (u *Updater) updateBinary(downloadedBinariesPath, basePath, binary string) } } - if err := os.Rename(currentBinaryPath, oldBinaryPath); err != nil { + if err := rename.Rename(currentBinaryPath, oldBinaryPath); err != nil { return fmt.Errorf("rename %s to %s: %w", currentBinaryPath, oldBinaryPath, err) } - if err := os.Rename(downloadedBinaryPath, currentBinaryPath); err != nil { - // Try to revert previous os.Rename - if err := os.Rename(oldBinaryPath, currentBinaryPath); err != nil { + if err := rename.Rename(downloadedBinaryPath, currentBinaryPath); err != nil { + // Try to revert previous rename. + if err := rename.Rename(oldBinaryPath, currentBinaryPath); err != nil { u.log.Errorf("Failed to rename file %q to %q: %v", oldBinaryPath, currentBinaryPath, err) } @@ -201,7 +202,7 @@ func (u *Updater) updateBinary(downloadedBinariesPath, basePath, binary string) func (u *Updater) restore(currentBinaryPath string, toBeRemoved string) { u.removeFiles(currentBinaryPath) - if err := os.Rename(toBeRemoved, currentBinaryPath); err != nil { + if err := rename.Rename(toBeRemoved, currentBinaryPath); err != nil { u.log.Errorf("Failed to rename file %q to %q: %v", toBeRemoved, currentBinaryPath, err) } }