Merge pull request #3675 from ItsMattL/update

Refactor file handing for self-update.
This commit is contained in:
MichaelEischer 2022-04-09 21:55:56 +02:00 committed by GitHub
commit c60a5f00c9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 69 additions and 24 deletions

View file

@ -0,0 +1,8 @@
Bugfix: Support self-update on Windows
Restic self-update would fail in situations where the operating system
locks running binaries, including Windows. The new behavior works around
this by renaming the running file and swapping the updated file in place.
https://github.com/restic/restic/issues/2248
https://github.com/restic/restic/pull/3675

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
}