mirror of
https://github.com/fosrl/newt.git
synced 2026-11-14 18:26:01 -06:00
71 lines
2.2 KiB
Go
71 lines
2.2 KiB
Go
//go:build windows
|
|
|
|
package updates
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/fosrl/newt/logger"
|
|
)
|
|
|
|
// oldBinaryPrefix returns the prefix used for previous binaries parked
|
|
// during an update. Each update gets a unique suffix because a parked
|
|
// binary can stay locked for a long time (for example by a service host
|
|
// process that supervises the updated one), and a locked file cannot be
|
|
// renamed over.
|
|
func oldBinaryPrefix(exePath string) string {
|
|
return exePath + ".old"
|
|
}
|
|
|
|
// replaceBinary swaps the binary at exePath for the one at newPath.
|
|
//
|
|
// Windows refuses to overwrite (or delete) an executable that is mapped by a
|
|
// running process, so renaming newPath directly over exePath fails with
|
|
// "Access is denied". A running executable can however be renamed, so we
|
|
// first move it aside and then move the new binary into its place. The
|
|
// parked copy is removed by removeStaleBinary once nothing is running it.
|
|
func replaceBinary(newPath, exePath string) error {
|
|
oldPath := fmt.Sprintf("%s-%d", oldBinaryPrefix(exePath), time.Now().UnixNano())
|
|
|
|
if err := os.Rename(exePath, oldPath); err != nil {
|
|
return fmt.Errorf("failed to move running binary aside: %w", err)
|
|
}
|
|
|
|
if err := os.Rename(newPath, exePath); err != nil {
|
|
// Put the original binary back so the next start still works.
|
|
if rbErr := os.Rename(oldPath, exePath); rbErr != nil {
|
|
return fmt.Errorf("failed to install new binary: %w (restoring previous binary also failed: %v)", err, rbErr)
|
|
}
|
|
return fmt.Errorf("failed to install new binary: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// removeStaleBinary deletes previous binaries left behind by replaceBinary.
|
|
// A parked binary can only be deleted once every process running it has
|
|
// exited, so this is best effort and anything still locked is retried on the
|
|
// next check.
|
|
func removeStaleBinary(exePath string) {
|
|
dir := filepath.Dir(exePath)
|
|
prefix := filepath.Base(oldBinaryPrefix(exePath))
|
|
|
|
entries, err := os.ReadDir(dir)
|
|
if err != nil {
|
|
return
|
|
}
|
|
for _, e := range entries {
|
|
if e.IsDir() || !strings.HasPrefix(e.Name(), prefix) {
|
|
continue
|
|
}
|
|
oldPath := filepath.Join(dir, e.Name())
|
|
if err := os.Remove(oldPath); err == nil {
|
|
logger.Debug("checkAndSelfUpdate: removed previous binary %s", oldPath)
|
|
}
|
|
}
|
|
}
|