Refactor file handing for self-update.

* Write new file payload to a temp file before touching the original
binary. Minimizes the possibility of failing mid-write and corrupting
the binary.
* On Windows, move the original binary out to a temp file rather than
removing it as the running binary is locked. Fixes issue #2248.
This commit is contained in:
Matt LaPlante
2021-08-23 12:21:09 -05:00
committed by Michael Eischer
parent 7d55b4f95e
commit 0ba9d4ced7
4 changed files with 69 additions and 24 deletions

View File

@@ -10,6 +10,7 @@ import (
"encoding/hex"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"runtime"
@@ -40,14 +41,6 @@ func findHash(buf []byte, filename string) (hash []byte, err error) {
}
func extractToFile(buf []byte, filename, target string, printf func(string, ...interface{})) error {
var mode = os.FileMode(0755)
// get information about the target file
fi, err := os.Lstat(target)
if err == nil {
mode = fi.Mode()
}
var rd io.Reader = bytes.NewReader(buf)
switch filepath.Ext(filename) {
case ".bz2":
@@ -74,33 +67,44 @@ func extractToFile(buf []byte, filename, target string, printf func(string, ...i
rd = file
}
err = os.Remove(target)
if os.IsNotExist(err) {
err = nil
}
if err != nil {
return fmt.Errorf("unable to remove target file: %v", err)
}
dest, err := os.OpenFile(target, os.O_CREATE|os.O_EXCL|os.O_WRONLY, mode)
// Write everything to a temp file
dir := filepath.Dir(target)
new, err := ioutil.TempFile(dir, "restic")
if err != nil {
return err
}
n, err := io.Copy(dest, rd)
n, err := io.Copy(new, rd)
if err != nil {
_ = dest.Close()
_ = os.Remove(dest.Name())
_ = new.Close()
_ = os.Remove(new.Name())
return err
}
if err = new.Sync(); err != nil {
return err
}
if err = new.Close(); err != nil {
return err
}
err = dest.Close()
if err != nil {
mode := os.FileMode(0755)
// attempt to find the original mode
if fi, err := os.Lstat(target); err == nil {
mode = fi.Mode()
}
// Remove the original binary.
if err := removeResticBinary(dir, target); err != nil {
return err
}
printf("saved %d bytes in %v\n", n, dest.Name())
return nil
// Rename the temp file to the final location atomically.
if err := os.Rename(new.Name(), target); err != nil {
return err
}
printf("saved %d bytes in %v\n", n, target)
return os.Chmod(target, mode)
}
// DownloadLatestStableRelease downloads the latest stable released version of

View File

@@ -0,0 +1,10 @@
//go:build !windows
// +build !windows
package selfupdate
// Remove the target binary.
func removeResticBinary(dir, target string) error {
// removed on rename on this platform
return nil
}

View File

@@ -0,0 +1,23 @@
//go:build windows
// +build windows
package selfupdate
import (
"fmt"
"os"
"path/filepath"
)
// Rename (rather than remove) the running version. The running binary will be locked
// on Windows and cannot be removed while still executing.
func removeResticBinary(dir, target string) error {
backup := filepath.Join(dir, filepath.Base(target)+".bak")
if _, err := os.Stat(backup); err == nil {
_ = os.Remove(backup)
}
if err := os.Rename(target, backup); err != nil {
return fmt.Errorf("unable to rename target file: %v", err)
}
return nil
}